From 0dadc643f694daaa0d79edf8ff3f8397691863d6 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Wed, 4 Sep 2013 16:18:09 -0400 Subject: [PATCH 01/21] first pass --- Source/Core/CorridorGeometryLibrary.js | 16 +- Source/Core/PolylineVolumeGeometry.js | 526 +++++++++++++++++++++++++ 2 files changed, 534 insertions(+), 8 deletions(-) create mode 100644 Source/Core/PolylineVolumeGeometry.js diff --git a/Source/Core/CorridorGeometryLibrary.js b/Source/Core/CorridorGeometryLibrary.js index ca85eba490da..2b6178cbc3ba 100644 --- a/Source/Core/CorridorGeometryLibrary.js +++ b/Source/Core/CorridorGeometryLibrary.js @@ -61,7 +61,7 @@ define([ var quaterion = new Quaternion(); var rotMatrix = new Matrix3(); - function computeRoundCorner (cornerPoint, startPoint, endPoint, cornerType, leftIsOutside, ellipsoid) { + function computeRoundCorner (cornerPoint, startPoint, endPoint, cornerType, leftIsOutside) { var angle = Cartesian3.angleBetween(startPoint.subtract(cornerPoint, scratch1), endPoint.subtract(cornerPoint, scratch2)); var granularity = (cornerType.value === CornerType.BEVELED.value) ? 1 : Math.ceil(angle / CesiumMath.toRadians(5)) + 1; @@ -100,7 +100,7 @@ define([ startPoint = Cartesian3.fromArray(calculatedPositions[1], leftEdge.length - 3, startPoint); endPoint = Cartesian3.fromArray(calculatedPositions[0], 0, endPoint); cornerPoint = startPoint.add(endPoint, cornerPoint).multiplyByScalar(0.5, cornerPoint); - var firstEndCap = computeRoundCorner(cornerPoint, startPoint, endPoint, CornerType.ROUNDED, false, ellipsoid); + var firstEndCap = computeRoundCorner(cornerPoint, startPoint, endPoint, CornerType.ROUNDED, false); var length = calculatedPositions.length - 1; var rightEdge = calculatedPositions[length - 1]; @@ -108,12 +108,12 @@ define([ startPoint = Cartesian3.fromArray(rightEdge, rightEdge.length - 3, startPoint); endPoint = Cartesian3.fromArray(leftEdge, 0, endPoint); cornerPoint = startPoint.add(endPoint, cornerPoint).multiplyByScalar(0.5, cornerPoint); - var lastEndCap = computeRoundCorner(cornerPoint, startPoint, endPoint, CornerType.ROUNDED, false, ellipsoid); + var lastEndCap = computeRoundCorner(cornerPoint, startPoint, endPoint, CornerType.ROUNDED, false); return [firstEndCap, lastEndCap]; } - function computeMiteredCorner (position, startPoint, leftCornerDirection, lastPoint, leftIsOutside, granularity, ellipsoid) { + function computeMiteredCorner (position, leftCornerDirection, lastPoint, leftIsOutside) { var cornerPoint = scratch1; if (leftIsOutside) { cornerPoint = Cartesian3.add(position, leftCornerDirection, cornerPoint); @@ -238,9 +238,9 @@ define([ leftPos = rightPos.add(left.multiplyByScalar(width * 2, leftPos), leftPos); previousPos = rightPos.add(left.multiplyByScalar(width, previousPos), previousPos); if (cornerType.value === CornerType.ROUNDED.value || cornerType.value === CornerType.BEVELED.value) { - corners.push({leftPositions : computeRoundCorner(rightPos, startPoint, leftPos, cornerType, leftIsOutside, ellipsoid)}); + corners.push({leftPositions : computeRoundCorner(rightPos, startPoint, leftPos, cornerType, leftIsOutside)}); } else { - corners.push({leftPositions : computeMiteredCorner(position, startPoint, cornerDirection.negate(cornerDirection), leftPos, leftIsOutside, granularity, ellipsoid)}); + corners.push({leftPositions : computeMiteredCorner(position, cornerDirection.negate(cornerDirection), leftPos, leftIsOutside)}); } } else { leftPos = Cartesian3.add(position, cornerDirection, leftPos); @@ -259,9 +259,9 @@ define([ rightPos = leftPos.add(left.multiplyByScalar(width * 2, rightPos).negate(rightPos), rightPos); previousPos = leftPos.add(left.multiplyByScalar(width, previousPos).negate(previousPos), previousPos); if (cornerType.value === CornerType.ROUNDED.value || cornerType.value === CornerType.BEVELED.value) { - corners.push({rightPositions : computeRoundCorner(leftPos, startPoint, rightPos, cornerType, leftIsOutside, ellipsoid)}); + corners.push({rightPositions : computeRoundCorner(leftPos, startPoint, rightPos, cornerType, leftIsOutside)}); } else { - corners.push({rightPositions : computeMiteredCorner(position, startPoint, cornerDirection, rightPos, leftIsOutside, granularity, ellipsoid)}); + corners.push({rightPositions : computeMiteredCorner(position, cornerDirection, rightPos, leftIsOutside)}); } } backward = forward.negate(backward); diff --git a/Source/Core/PolylineVolumeGeometry.js b/Source/Core/PolylineVolumeGeometry.js new file mode 100644 index 000000000000..c0ac2129aa85 --- /dev/null +++ b/Source/Core/PolylineVolumeGeometry.js @@ -0,0 +1,526 @@ +/*global define*/ +define([ + './defined', + './DeveloperError', + './Cartesian2', + './Cartesian3', + './Cartesian4', + './CornerType', + './ComponentDatatype', + './Ellipsoid', + './EllipsoidTangentPlane', + './Geometry', + './GeometryPipeline', + './IndexDatatype', + './Math', + './Matrix3', + './Matrix4', + './PolygonPipeline', + './PolylinePipeline', + './PrimitiveType', + './Quaternion', + './Transforms', + './defaultValue', + './BoundingSphere', + './BoundingRectangle', + './GeometryAttribute', + './GeometryAttributes', + './VertexFormat', + './WindingOrder' + ], function( + defined, + DeveloperError, + Cartesian2, + Cartesian3, + Cartesian4, + CornerType, + ComponentDatatype, + Ellipsoid, + EllipsoidTangentPlane, + Geometry, + GeometryPipeline, + IndexDatatype, + CesiumMath, + Matrix3, + Matrix4, + PolygonPipeline, + PolylinePipeline, + PrimitiveType, + Quaternion, + Transforms, + defaultValue, + BoundingSphere, + BoundingRectangle, + GeometryAttribute, + GeometryAttributes, + VertexFormat, + WindingOrder) { + "use strict"; + + var scratchCartesian1 = new Cartesian3(); + var scratchCartesian2 = new Cartesian3(); + var scratchCartesian3 = new Cartesian3(); + var scratchCartesian4 = new Cartesian3(); + var scratchCartesian5 = new Cartesian3(); + var scratchCartesian6 = new Cartesian3(); + var scratchCartesian7 = new Cartesian3(); + var scratchCartesian8 = new Cartesian3(); + var scratchCartesian9 = new Cartesian3(); + + var scratch1 = new Cartesian3(); + var scratch2 = new Cartesian3(); + var scratch3 = new Cartesian3(); + + var scratch2Array = [new Cartesian3(), new Cartesian3()]; + + var rotation = new Matrix3(); + var translation = new Matrix4(); + var transform = new Matrix4(); + var scale = new Matrix3(); + var scaleCartesian = new Cartesian3(1, 1, 1); + var negX = Cartesian4.UNIT_X.negate(); + function addPositions(centers, left, shape, finalPositions, ellipsoid, height) { + var scalar = left.magnitude(); + scaleCartesian.x = scalar; + scale = Matrix3.fromScale(scaleCartesian, scale); + var position = scratch1; + var west = scratch2; + var finalPosition = scratch3; + var i, j; + var shapeLength = shape.length; + for (j = 0; j < centers.length; j+=3) { + position = Cartesian3.fromArray(centers, j, position); + transform = Transforms.eastNorthUpToFixedFrame(position, ellipsoid, transform); + west = Matrix4.multiplyByVector(transform, negX, west); + west = Cartesian3.fromCartesian4(west).normalize(west); + var angle = computeRotationAngle(west, left, position, ellipsoid); + rotation = Matrix3.fromRotationZ(angle, rotation); + transform = Matrix4.multiply(transform, Matrix4.fromRotationTranslation(rotation, new Cartesian3(0.0, 0.0, height), translation), transform); + for(i = 0; i < shapeLength; i+=3) { + finalPosition = Cartesian3.fromArray(shape, i, finalPosition); + finalPosition = Matrix4.multiplyByPoint(transform, finalPosition, finalPosition); + finalPosition = Matrix3.multiplyByVector(scale, finalPosition, finalPosition); + finalPositions.push(finalPosition.x, finalPosition.y, finalPosition.z); + } + } + return finalPositions; + } + + function convertShapeTo3DSides(shape2D, boundingRectangle) { //orientate 2D shape to XZ plane center at (0, 0, 0) + var length = shape2D.length; + var shape = new Array(length * 3); + var index = 0; + + shape[index++] = shape2D[0].x - (boundingRectangle.x + (boundingRectangle.width / 2)); + shape[index++] = 0; + shape[index++] = shape2D[0].y - (boundingRectangle.y + (boundingRectangle.height / 2)); + for (var i = 1; i < length; i++) { + shape[index++] = shape2D[i].x - (boundingRectangle.x + (boundingRectangle.width / 2)); + shape[index++] = 0; + shape[index++] = shape2D[i].y - (boundingRectangle.y + (boundingRectangle.height / 2)); + + shape[index++] = shape2D[i].x - (boundingRectangle.x + (boundingRectangle.width / 2)); + shape[index++] = 0; + shape[index++] = shape2D[i].y - (boundingRectangle.y + (boundingRectangle.height / 2)); + } + shape[index++] = shape2D[0].x - (boundingRectangle.x + (boundingRectangle.width / 2)); + shape[index++] = 0; + shape[index++] = shape2D[0].y - (boundingRectangle.y + (boundingRectangle.height / 2)); + + return shape; + } + + + function convertShapeTo3DFirstLast(shape2D, boundingRectangle) { //orientate 2D shape to XZ plane center at (0, 0, 0) + var length = shape2D.length; + var shape = new Array(length * 3); + var index = 0; + + for (var i = 0; i < length; i++) { + shape[index++] = shape2D[i].x - (boundingRectangle.x + (boundingRectangle.width / 2)); + shape[index++] = 0; + shape[index++] = shape2D[i].y - (boundingRectangle.y + (boundingRectangle.height / 2)); + } + + return shape; + } + + var originScratch = new Cartesian2(); + var nextScratch = new Cartesian2(); + var prevScratch = new Cartesian2(); + function angleIsGreaterThanPi (forward, backward, position, ellipsoid) { + var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid); + var origin = tangentPlane.projectPointOntoPlane(position, originScratch); + var next = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, forward, nextScratch), nextScratch); + var prev = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, backward, prevScratch), prevScratch); + + prev = prev.subtract(origin, prev); + next = next.subtract(origin, next); + + return ((prev.x * next.y) - (prev.y * next.x)) >= 0.0; + } + + + function computeRotationAngle (start, end, position, ellipsoid) { + var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid); + var origin = tangentPlane.projectPointOntoPlane(position, originScratch); + var next = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, start, nextScratch), nextScratch); + var prev = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, end, prevScratch), prevScratch); + prev = prev.subtract(origin, prev); + next = next.subtract(origin, next); + + var angle = Cartesian2.angleBetween(next, prev); + + return (((prev.x * next.y) - (prev.y * next.x)) >= 0.0) ? -angle : angle; + } + + var posScratch = new Cartesian3(); + function scaleToSurface (positions, ellipsoid){ + for (var i = 0; i < positions.length; i += 3) { + posScratch = Cartesian3.fromArray(positions, i, posScratch); + posScratch = ellipsoid.scaleToGeodeticSurface(posScratch, posScratch); + positions[i] = posScratch.x; + positions[i + 1] = posScratch.y; + positions[i + 2] = posScratch.z; + } + + return positions; + } + + var quaterion = new Quaternion(); + var rotMatrix = new Matrix3(); + var scratch3Array = new Array(3); + function computeRoundCorner(pivot, startPoint, endPoint, cornerType, leftIsOutside, ellipsoid, finalPositions, shape, height) { + var angle = Cartesian3.angleBetween(startPoint.subtract(pivot, scratch1), endPoint.subtract(pivot, scratch2)); + var granularity = (cornerType.value === CornerType.BEVELED.value) ? 0 : Math.ceil(angle/CesiumMath.toRadians(5)); + + var m; + if (leftIsOutside) { + m = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(pivot, angle/(granularity+1), quaterion), rotMatrix); + } else { + m = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(pivot.negate(scratch1), angle/(granularity+1), quaterion), rotMatrix); + } + + startPoint = startPoint.clone(scratch1); + for (var i = 0; i < granularity; i++) { + startPoint = m.multiplyByVector(startPoint, startPoint); + scratch3Array[0] = startPoint.x; + scratch3Array[1] = startPoint.y; + scratch3Array[2] = startPoint.z; + + var left = startPoint.sutract(pivot, scratch1); + if (!leftIsOutside) { + left = left.negate(left); + } + scratch3Array = scaleToSurface(scratch3Array, ellipsoid); + finalPositions = addPositions(scratch3Array, left, shape, finalPositions, ellipsoid, height); + } + + return finalPositions; + } + + + function computeMiteredCorner(position, leftCornerDirection, leftIsOutside, ellipsoid, finalPositions, shape, height) { + var corner = scratch1; + if (!leftIsOutside) { + leftCornerDirection = leftCornerDirection.negate(leftCornerDirection); + } + corner = Cartesian3.add(position, leftCornerDirection, corner); + scratch3Array[0] = corner.x; + scratch3Array[1] = corner.y; + scratch3Array[2] = corner.z; + + finalPositions = addPositions(scratch3Array, leftCornerDirection, shape, finalPositions, ellipsoid, height); + + return finalPositions; + } + + function reverseIndices(indices) { + for(var i = 0; i < indices.length; i += 3) { + var temp = indices[i]; + indices[i] = indices[i + 2]; + indices[i + 2] = temp; + } + return indices; + } + + function computeAttributes(positions, ends, shape, boundingRectangle, vertexFormat, ellipsoid) { + var attributes = new GeometryAttributes(); + if (vertexFormat.position) { + attributes.position = new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : new Float64Array(positions.concat(ends)) + }); + } + + var shapeLength = shape.length; + var indices = []; + var length = positions.length/(shapeLength*6); + var i, j; + var LL, UL, UR, LR; + var offset = shapeLength * 2; + for (i = 0; i < length - 1; i++) { + for (j = 0; j < shapeLength - 1; j++) { + LL = j * 2 + i * shapeLength; + UL = LL + 1; + UR = UL + offset; + LR = LL + offset; + + indices.push(UL, LL, UR, UR, LL, LR); + } + LL = shapeLength * 2 - 2 + i * shapeLength; + UL = LL + 1; + UR = UL + offset; + LR = LL + offset; + + indices.push(UL, LL, UR, UR, LL, LR); + } + + if (vertexFormat.st) { + var st = []; + // var lengthSt = 1 / (length - 1); + // var heightSt = 1 / (boundingRectangle.height); +/* + for (i = 0; i < length; i++) { + var t = i * lengthSt; + for(j = 0; j < shapeLength; j++) { + var s = heightSt * (shape[j].y + boundingRectangle.height/2); + st.push(s, t); + } + if (i === 0 || i === length - 1) { + for(j = 0; j < shapeLength; j++) { + var s = heightSt * (shape[j].y + boundingRectangle.height/2); + st.push(s, t); + } + } + } + st = st.concat(st); +*/ + for (i = 0; i < attributes.position.values.length/3*2; i++) { + st.push(0); + } + attributes.st = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2, + values : new Float32Array(st) + }); + } + + var endOffset = positions.length / 3; + var firstEndIndices = PolygonPipeline.triangulate(shape); + for (i = 0; i < firstEndIndices.length; i += 3) { + var v0 = firstEndIndices[i]; + var v1 = firstEndIndices[i + 1]; + var v2 = firstEndIndices[i + 2]; + + indices.push(v0 + endOffset, v1 + endOffset, v2 + endOffset, v2 + endOffset + shapeLength, v1 + endOffset + shapeLength, v0 + endOffset + shapeLength); + } + + var geometry = new Geometry({ + attributes: attributes, + indices: IndexDatatype.createTypedArray(positions.length/3, indices), + boundingSphere: BoundingSphere.fromVertices(positions), + primitiveType: PrimitiveType.TRIANGLES + }); + + if (vertexFormat.normal) { + geometry = GeometryPipeline.computeNormal(geometry); + /*var normals = []; + for (i = 0; i < positions.length; i+=3) { + var pos = Cartesian3.fromArray(positions, i); + var n = ellipsoid.geodeticSurfaceNormal(pos); + normals.push(n.x, n.y, n.z); + } + + geometry.attributes.normal = new GeometryAttribute({ + componentDatatype: ComponentDatatype.FLOAT, + componentsPerAttribute: 3, + values: new Float32Array(normals) + });*/ + } + + if (vertexFormat.tangent || vertexFormat.binormal) { + geometry = GeometryPipeline.computeBinormalAndTangent(geometry); + } + + return geometry; + } + + var brScratch = new BoundingRectangle(); + function computePositions(positions, geometry) { + var granularity = geometry._granularity; + var cornerType = geometry._cornerType; + var ellipsoid = geometry._ellipsoid; + var shape2D = geometry._shape; + var windingOrder = PolygonPipeline.computeWindingOrder2D(shape2D); + if (windingOrder.value === WindingOrder.CLOCKWISE.value) { + shape2D.reverse(); + } + + var boundingRectangle = BoundingRectangle.fromPoints(shape2D, brScratch); + var shapeForSides = convertShapeTo3DSides(shape2D, boundingRectangle); + var shapeForEnds = convertShapeTo3DFirstLast(shape2D, boundingRectangle); + var height = geometry._height + boundingRectangle.height/2; + var width = boundingRectangle.width/2; + var finalPositions = []; + var centers = []; + var ends = []; + + var i; + var length = positions.length; + var forward = scratchCartesian1; + var backward = scratchCartesian2; + var cornerDirection = scratchCartesian3; + var surfaceNormal = scratchCartesian4; + var pivot = scratchCartesian5; + var start = scratchCartesian6; + var end = scratchCartesian7; + var left = scratchCartesian8; + var previousPosition = scratchCartesian9; + + var position = positions[0]; + var nextPosition = positions[1]; + surfaceNormal = ellipsoid.geodeticSurfaceNormal(position, surfaceNormal); + forward = Cartesian3.subtract(nextPosition, position, forward).normalize(forward); + left = surfaceNormal.cross(forward, left).normalize(left); + + ends = addPositions([position.x, position.y, position.z], left, shapeForEnds, ends, ellipsoid, height); + previousPosition = position.clone(previousPosition); + position = nextPosition; + backward = forward.negate(backward); + + var surfacePositions; + + for (i = 1; i < length - 1; i++) { + nextPosition = positions[i + 1]; + forward = Cartesian3.subtract(nextPosition, position, forward).normalize(forward); + cornerDirection = forward.add(backward, cornerDirection).normalzie(cornerDirection); + surfaceNormal = ellipsoid.geodeticSurfaceNormal(position, surfaceNormal); + var doCorner = !Cartesian3.equalsEpsilon(cornerDirection.negate(scratch1), surfaceNormal, CesiumMath.EPSILON2); + if (doCorner) { + cornerDirection = cornerDirection.cross(surfaceNormal, cornerDirection); + cornerDirection = surfaceNormal.cross(cornerDirection, cornerDirection); + var scalar = width / Math.max(0.25, (Cartesian3.cross(cornerDirection, backward, scratch1).magnitude())); + var leftIsOutside = angleIsGreaterThanPi(forward, backward, position, ellipsoid); + cornerDirection = cornerDirection.multiplyByScalar(scalar, cornerDirection, cornerDirection); + if (leftIsOutside) { + pivot = Cartesian3.add(position, cornerDirection, pivot); + start = pivot.add(left.multiplyByScalar(width, start), start); + scratch2Array[0] = previousPosition.clone(scratch2Array[0]); + scratch2Array[1] = start.clone(scratch2Array[1]); + surfacePositions = PolylinePipeline.scaleToSurface(scratch2Array); + finalPositions = addPositions(surfacePositions, left, shapeForSides, finalPositions, ellipsoid, height); + centers = centers.concat(surfacePositions); + left = surfaceNormal.cross(forward, left).normalize(left); + end = pivot.add(left.multiplyByScalar(width, end), end); + if (cornerType.value === CornerType.ROUNDED.value || cornerType.value === CornerType.BEVELED.value) { + computeRoundCorner(pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, height); + } else { + computeMiteredCorner(position, cornerDirection.negate(cornerDirection), leftIsOutside, ellipsoid, finalPositions, shapeForSides, height); + } + previousPosition = end.clone(previousPosition); + } else { + pivot = Cartesian3.add(position, cornerDirection, pivot); + start = pivot.add(left.multiplyByScalar(-width, start), start); + scratch2Array[0] = previousPosition.clone(scratch2Array[0]); + scratch2Array[1] = start.clone(scratch2Array[1]); + surfacePositions = PolylinePipeline.scaleToSurface(scratch2Array, granularity, ellipsoid); + finalPositions = addPositions(surfacePositions, left, shapeForSides, finalPositions, ellipsoid, height); + centers = centers.concat(surfacePositions); + left = surfaceNormal.cross(forward, left).normalize(left); + end = pivot.add(left.multiplyByScalar(-width, end), end); + if (cornerType.value === CornerType.ROUNDED.value || cornerType.value === CornerType.BEVELED.value) { + computeRoundCorner(pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, height); + } else { + computeMiteredCorner(position, cornerDirection, leftIsOutside, ellipsoid, finalPositions, shapeForSides, height); + } + previousPosition = end.clone(previousPosition); + } + backward = forward.negate(backward); + } + position = nextPosition; + } + scratch2Array[0] = previousPosition.clone(scratch2Array[0]); + scratch2Array[1] = position.clone(scratch2Array[1]); + centers = PolylinePipeline.scaleToSurface(scratch2Array, granularity, ellipsoid); + finalPositions = addPositions(centers, left, shapeForSides, finalPositions, ellipsoid, height); + ends = addPositions([position.x, position.y, position.z], left, shapeForEnds, ends, ellipsoid, height); + + return computeAttributes(finalPositions, ends, shape2D, boundingRectangle, geometry._vertexFormat, ellipsoid); + } + + /** + * A description of a polyline volume. + * + * @alias PolylineVolumeGeometry + * @constructor + * + * @param {Array} options.polylinePositions An array of {Cartesain3} positions that define the center of the polyline volume. + * @param {Number} options.shapePositions An array of {Cartesian2} positions that define the shape to be extruded along the polyline + * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference. + * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer. + * @param {Number} [options.height=0] The distance between the ellipsoid surface and the positions. + * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. + * @param {Boolean} [options.cornerType = CornerType.ROUNDED] Determines the style of the corners. + * + * @exception {DeveloperError} options.positions is required. + * @exception {DeveloperError} options.width is required. + * + * @see PolylineVolumeGeometry#createGeometry + * + * @example + * var tube = new PolylineVolumeGeometry({ + * vertexFormat : VertexFormat.POSITION_ONLY, + * positions : ellipsoid.cartographicArrayToCartesianArray([ + * Cartographic.fromDegrees(-72.0, 40.0), + * Cartographic.fromDegrees(-70.0, 35.0) + * ]), + * width : 100000 + * }); + */ + var PolylineVolumeGeometry = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + var positions = options.polylinePositions; + if (!defined(positions)) { + throw new DeveloperError('options.polylinePositions is required.'); + } + var shape = options.shapePositions; + if (!defined(shape)) { + throw new DeveloperError('options.shapePositions is required.'); + } + + this._positions = positions; + this._shape = shape; + this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); + this._height = defaultValue(options.height, 0); + this._cornerType = defaultValue(options.cornerTYpe, CornerType.ROUNDED); + this._vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT); + this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); + this._workerName = 'createPolylineVolumeGeometry'; + + }; + + /** + * Computes the geometric representation of a corridor, including its vertices, indices, and a bounding sphere. + * @memberof PolylineVolumeGeometry + * + * @param {PolylineVolumeGeometry} polylineVolumeGeometry A description of the polylineVolume. + * + * @returns {Geometry} The computed vertices and indices. + * + * @exception {DeveloperError} Count of unique positions must be greater than 1. + */ + PolylineVolumeGeometry.createGeometry = function(polylineVolumeGeometry) { + var positions = polylineVolumeGeometry._positions; + var cleanPositions = PolylinePipeline.removeDuplicates(positions); + if (cleanPositions.length < 2) { + throw new DeveloperError('Count of unique positions must be greater than 1.'); + } + + return computePositions(cleanPositions, polylineVolumeGeometry); + }; + + return PolylineVolumeGeometry; + +}); \ No newline at end of file From 7e60c8105e9313c67e120b9c577aaf8328e804d3 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Wed, 4 Sep 2013 17:47:57 -0400 Subject: [PATCH 02/21] working polyline volume --- Source/Core/PolylineVolumeGeometry.js | 117 ++++++++++---------------- 1 file changed, 46 insertions(+), 71 deletions(-) diff --git a/Source/Core/PolylineVolumeGeometry.js b/Source/Core/PolylineVolumeGeometry.js index c0ac2129aa85..e3b62f92045b 100644 --- a/Source/Core/PolylineVolumeGeometry.js +++ b/Source/Core/PolylineVolumeGeometry.js @@ -79,8 +79,7 @@ define([ var scale = new Matrix3(); var scaleCartesian = new Cartesian3(1, 1, 1); var negX = Cartesian4.UNIT_X.negate(); - function addPositions(centers, left, shape, finalPositions, ellipsoid, height) { - var scalar = left.magnitude(); + function addPositions(centers, left, shape, finalPositions, ellipsoid, height, scalar) { //TODO: duplicate corner poitns scaleCartesian.x = scalar; scale = Matrix3.fromScale(scaleCartesian, scale); var position = scratch1; @@ -98,15 +97,15 @@ define([ transform = Matrix4.multiply(transform, Matrix4.fromRotationTranslation(rotation, new Cartesian3(0.0, 0.0, height), translation), transform); for(i = 0; i < shapeLength; i+=3) { finalPosition = Cartesian3.fromArray(shape, i, finalPosition); - finalPosition = Matrix4.multiplyByPoint(transform, finalPosition, finalPosition); finalPosition = Matrix3.multiplyByVector(scale, finalPosition, finalPosition); + finalPosition = Matrix4.multiplyByPoint(transform, finalPosition, finalPosition); finalPositions.push(finalPosition.x, finalPosition.y, finalPosition.z); } } return finalPositions; } - function convertShapeTo3DSides(shape2D, boundingRectangle) { //orientate 2D shape to XZ plane center at (0, 0, 0) + function convertShapeTo3DSides(shape2D, boundingRectangle) { //orientate 2D shape to XZ plane center at (0, 0, 0), duplicate points var length = shape2D.length; var shape = new Array(length * 3); var index = 0; @@ -160,7 +159,6 @@ define([ return ((prev.x * next.y) - (prev.y * next.x)) >= 0.0; } - function computeRotationAngle (start, end, position, ellipsoid) { var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid); var origin = tangentPlane.projectPointOntoPlane(position, originScratch); @@ -208,42 +206,17 @@ define([ scratch3Array[1] = startPoint.y; scratch3Array[2] = startPoint.z; - var left = startPoint.sutract(pivot, scratch1); + var left = startPoint.subtract(pivot).normalize(); if (!leftIsOutside) { left = left.negate(left); } scratch3Array = scaleToSurface(scratch3Array, ellipsoid); - finalPositions = addPositions(scratch3Array, left, shape, finalPositions, ellipsoid, height); - } - - return finalPositions; - } - - - function computeMiteredCorner(position, leftCornerDirection, leftIsOutside, ellipsoid, finalPositions, shape, height) { - var corner = scratch1; - if (!leftIsOutside) { - leftCornerDirection = leftCornerDirection.negate(leftCornerDirection); + finalPositions = addPositions(scratch3Array, left, shape, finalPositions, ellipsoid, height, 1); } - corner = Cartesian3.add(position, leftCornerDirection, corner); - scratch3Array[0] = corner.x; - scratch3Array[1] = corner.y; - scratch3Array[2] = corner.z; - - finalPositions = addPositions(scratch3Array, leftCornerDirection, shape, finalPositions, ellipsoid, height); return finalPositions; } - function reverseIndices(indices) { - for(var i = 0; i < indices.length; i += 3) { - var temp = indices[i]; - indices[i] = indices[i + 2]; - indices[i + 2] = temp; - } - return indices; - } - function computeAttributes(positions, ends, shape, boundingRectangle, vertexFormat, ellipsoid) { var attributes = new GeometryAttributes(); if (vertexFormat.position) { @@ -262,14 +235,14 @@ define([ var offset = shapeLength * 2; for (i = 0; i < length - 1; i++) { for (j = 0; j < shapeLength - 1; j++) { - LL = j * 2 + i * shapeLength; + LL = j * 2 + i * shapeLength * 2; UL = LL + 1; UR = UL + offset; LR = LL + offset; indices.push(UL, LL, UR, UR, LL, LR); } - LL = shapeLength * 2 - 2 + i * shapeLength; + LL = shapeLength * 2 - 2 + i * shapeLength * 2; UL = LL + 1; UR = UL + offset; LR = LL + offset; @@ -279,27 +252,33 @@ define([ if (vertexFormat.st) { var st = []; - // var lengthSt = 1 / (length - 1); - // var heightSt = 1 / (boundingRectangle.height); -/* + var lengthSt = 1 / (length - 1); + var heightSt = 1 / (boundingRectangle.height); + + var s, t; for (i = 0; i < length; i++) { - var t = i * lengthSt; - for(j = 0; j < shapeLength; j++) { - var s = heightSt * (shape[j].y + boundingRectangle.height/2); + t = i * lengthSt; + s = heightSt * (shape[0].y + boundingRectangle.height/2); + st.push(s, t); + for(j = 1; j < shapeLength; j++) { + s = heightSt * (shape[j].y + boundingRectangle.height/2); + st.push(s, t); st.push(s, t); } - if (i === 0 || i === length - 1) { - for(j = 0; j < shapeLength; j++) { - var s = heightSt * (shape[j].y + boundingRectangle.height/2); - st.push(s, t); - } - } + s = heightSt * (shape[0].y + boundingRectangle.height/2); + st.push(s, t); + } + for(j = 0; j < shapeLength; j++) { + t = 0; + s = heightSt * (shape[j].y + boundingRectangle.height/2); + st.push(s, t); } - st = st.concat(st); -*/ - for (i = 0; i < attributes.position.values.length/3*2; i++) { - st.push(0); + for(j = 0; j < shapeLength; j++) { + t = (length - 1) * lengthSt; + s = heightSt * (shape[j].y + boundingRectangle.height/2); + st.push(s, t); } + attributes.st = new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 2, @@ -326,18 +305,6 @@ define([ if (vertexFormat.normal) { geometry = GeometryPipeline.computeNormal(geometry); - /*var normals = []; - for (i = 0; i < positions.length; i+=3) { - var pos = Cartesian3.fromArray(positions, i); - var n = ellipsoid.geodeticSurfaceNormal(pos); - normals.push(n.x, n.y, n.z); - } - - geometry.attributes.normal = new GeometryAttribute({ - componentDatatype: ComponentDatatype.FLOAT, - componentsPerAttribute: 3, - values: new Float32Array(normals) - });*/ } if (vertexFormat.tangent || vertexFormat.binormal) { @@ -385,7 +352,7 @@ define([ forward = Cartesian3.subtract(nextPosition, position, forward).normalize(forward); left = surfaceNormal.cross(forward, left).normalize(left); - ends = addPositions([position.x, position.y, position.z], left, shapeForEnds, ends, ellipsoid, height); + ends = addPositions([position.x, position.y, position.z], left, shapeForEnds, ends, ellipsoid, height, 1); previousPosition = position.clone(previousPosition); position = nextPosition; backward = forward.negate(backward); @@ -395,7 +362,7 @@ define([ for (i = 1; i < length - 1; i++) { nextPosition = positions[i + 1]; forward = Cartesian3.subtract(nextPosition, position, forward).normalize(forward); - cornerDirection = forward.add(backward, cornerDirection).normalzie(cornerDirection); + cornerDirection = forward.add(backward, cornerDirection).normalize(cornerDirection); surfaceNormal = ellipsoid.geodeticSurfaceNormal(position, surfaceNormal); var doCorner = !Cartesian3.equalsEpsilon(cornerDirection.negate(scratch1), surfaceNormal, CesiumMath.EPSILON2); if (doCorner) { @@ -410,14 +377,18 @@ define([ scratch2Array[0] = previousPosition.clone(scratch2Array[0]); scratch2Array[1] = start.clone(scratch2Array[1]); surfacePositions = PolylinePipeline.scaleToSurface(scratch2Array); - finalPositions = addPositions(surfacePositions, left, shapeForSides, finalPositions, ellipsoid, height); + finalPositions = addPositions(surfacePositions, left, shapeForSides, finalPositions, ellipsoid, height, 1); centers = centers.concat(surfacePositions); left = surfaceNormal.cross(forward, left).normalize(left); end = pivot.add(left.multiplyByScalar(width, end), end); if (cornerType.value === CornerType.ROUNDED.value || cornerType.value === CornerType.BEVELED.value) { computeRoundCorner(pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, height); } else { - computeMiteredCorner(position, cornerDirection.negate(cornerDirection), leftIsOutside, ellipsoid, finalPositions, shapeForSides, height); + scratch3Array[0] = position.x; + scratch3Array[1] = position.y; + scratch3Array[2] = position.z; + + finalPositions = addPositions(scratch3Array, cornerDirection.negate(cornerDirection), shapeForSides, finalPositions, ellipsoid, height, scalar/width); } previousPosition = end.clone(previousPosition); } else { @@ -426,14 +397,18 @@ define([ scratch2Array[0] = previousPosition.clone(scratch2Array[0]); scratch2Array[1] = start.clone(scratch2Array[1]); surfacePositions = PolylinePipeline.scaleToSurface(scratch2Array, granularity, ellipsoid); - finalPositions = addPositions(surfacePositions, left, shapeForSides, finalPositions, ellipsoid, height); + finalPositions = addPositions(surfacePositions, left, shapeForSides, finalPositions, ellipsoid, height, 1); centers = centers.concat(surfacePositions); left = surfaceNormal.cross(forward, left).normalize(left); end = pivot.add(left.multiplyByScalar(-width, end), end); if (cornerType.value === CornerType.ROUNDED.value || cornerType.value === CornerType.BEVELED.value) { computeRoundCorner(pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, height); } else { - computeMiteredCorner(position, cornerDirection, leftIsOutside, ellipsoid, finalPositions, shapeForSides, height); + scratch3Array[0] = position.x; + scratch3Array[1] = position.y; + scratch3Array[2] = position.z; + + finalPositions = addPositions(scratch3Array, cornerDirection, shapeForSides, finalPositions, ellipsoid, height, scalar/width); } previousPosition = end.clone(previousPosition); } @@ -444,8 +419,8 @@ define([ scratch2Array[0] = previousPosition.clone(scratch2Array[0]); scratch2Array[1] = position.clone(scratch2Array[1]); centers = PolylinePipeline.scaleToSurface(scratch2Array, granularity, ellipsoid); - finalPositions = addPositions(centers, left, shapeForSides, finalPositions, ellipsoid, height); - ends = addPositions([position.x, position.y, position.z], left, shapeForEnds, ends, ellipsoid, height); + finalPositions = addPositions(centers, left, shapeForSides, finalPositions, ellipsoid, height, 1); + ends = addPositions([position.x, position.y, position.z], left, shapeForEnds, ends, ellipsoid, height, 1); return computeAttributes(finalPositions, ends, shape2D, boundingRectangle, geometry._vertexFormat, ellipsoid); } @@ -494,7 +469,7 @@ define([ this._shape = shape; this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); this._height = defaultValue(options.height, 0); - this._cornerType = defaultValue(options.cornerTYpe, CornerType.ROUNDED); + this._cornerType = defaultValue(options.cornerType, CornerType.ROUNDED); this._vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT); this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); this._workerName = 'createPolylineVolumeGeometry'; From bf506b577561d02baf9eadd935a89e7ebcc3fd57 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Thu, 5 Sep 2013 12:13:12 -0400 Subject: [PATCH 03/21] cleanup formatting --- Source/Core/CorridorGeometryLibrary.js | 21 +- Source/Core/PolylineVolumeGeometry.js | 333 +++++++++---------- Source/Core/PolylineVolumeGeometryLibrary.js | 58 ++++ 3 files changed, 219 insertions(+), 193 deletions(-) create mode 100644 Source/Core/PolylineVolumeGeometryLibrary.js diff --git a/Source/Core/CorridorGeometryLibrary.js b/Source/Core/CorridorGeometryLibrary.js index 2b6178cbc3ba..dd069eabb93e 100644 --- a/Source/Core/CorridorGeometryLibrary.js +++ b/Source/Core/CorridorGeometryLibrary.js @@ -6,6 +6,7 @@ define([ './CornerType', './EllipsoidTangentPlane', './PolylinePipeline', + './PolylineVolumeGeometryLibrary', './Matrix3', './Quaternion', './Math' @@ -16,6 +17,7 @@ define([ CornerType, EllipsoidTangentPlane, PolylinePipeline, + PolylineVolumeGeometryLibrary, Matrix3, Quaternion, CesiumMath) { @@ -44,21 +46,6 @@ define([ var cartesian9 = new Cartesian3(); var cartesian10 = new Cartesian3(); - var originScratch = new Cartesian3(); - var nextScratch = new Cartesian3(); - var prevScratch = new Cartesian3(); - function angleIsGreaterThanPi (forward, backward, position, ellipsoid) { - var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid); - var origin = tangentPlane.projectPointOntoPlane(position, originScratch); - var next = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, forward, nextScratch), nextScratch); - var prev = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, backward, prevScratch), prevScratch); - - prev = Cartesian2.subtract(prev, origin, prev); - next = Cartesian2.subtract(next, origin, next); - - return ((prev.x * next.y) - (prev.y * next.x)) >= 0.0; - } - var quaterion = new Quaternion(); var rotMatrix = new Matrix3(); function computeRoundCorner (cornerPoint, startPoint, endPoint, cornerType, leftIsOutside) { @@ -219,8 +206,8 @@ define([ cornerDirection = cornerDirection.cross(normal, cornerDirection); cornerDirection = normal.cross(cornerDirection, cornerDirection); var scalar = width / Math.max(0.25, (Cartesian3.cross(cornerDirection, backward, scratch1).magnitude())); - var leftIsOutside = angleIsGreaterThanPi(forward, backward, position, ellipsoid); - cornerDirection = cornerDirection.multiplyByScalar(scalar, cornerDirection, cornerDirection); + var leftIsOutside = PolylineVolumeGeometryLibrary.angleIsGreaterThanPi(forward, backward, position, ellipsoid); + cornerDirection = cornerDirection.multiplyByScalar(scalar, cornerDirection); if (leftIsOutside) { rightPos = Cartesian3.add(position, cornerDirection, rightPos); center = rightPos.add(left.multiplyByScalar(width, center), center); diff --git a/Source/Core/PolylineVolumeGeometry.js b/Source/Core/PolylineVolumeGeometry.js index e3b62f92045b..874d6e625906 100644 --- a/Source/Core/PolylineVolumeGeometry.js +++ b/Source/Core/PolylineVolumeGeometry.js @@ -17,6 +17,7 @@ define([ './Matrix4', './PolygonPipeline', './PolylinePipeline', + './PolylineVolumeGeometryLibrary', './PrimitiveType', './Quaternion', './Transforms', @@ -45,6 +46,7 @@ define([ Matrix4, PolygonPipeline, PolylinePipeline, + PolylineVolumeGeometryLibrary, PrimitiveType, Quaternion, Transforms, @@ -57,6 +59,8 @@ define([ WindingOrder) { "use strict"; + var scratch2Array = [new Cartesian3(), new Cartesian3()]; + var scratchCartesian1 = new Cartesian3(); var scratchCartesian2 = new Cartesian3(); var scratchCartesian3 = new Cartesian3(); @@ -69,149 +73,121 @@ define([ var scratch1 = new Cartesian3(); var scratch2 = new Cartesian3(); - var scratch3 = new Cartesian3(); - var scratch2Array = [new Cartesian3(), new Cartesian3()]; - - var rotation = new Matrix3(); - var translation = new Matrix4(); + var negativeX = Cartesian4.UNIT_X.negate(); var transform = new Matrix4(); - var scale = new Matrix3(); - var scaleCartesian = new Cartesian3(1, 1, 1); - var negX = Cartesian4.UNIT_X.negate(); - function addPositions(centers, left, shape, finalPositions, ellipsoid, height, scalar) { //TODO: duplicate corner poitns - scaleCartesian.x = scalar; - scale = Matrix3.fromScale(scaleCartesian, scale); - var position = scratch1; - var west = scratch2; - var finalPosition = scratch3; - var i, j; - var shapeLength = shape.length; - for (j = 0; j < centers.length; j+=3) { - position = Cartesian3.fromArray(centers, j, position); - transform = Transforms.eastNorthUpToFixedFrame(position, ellipsoid, transform); - west = Matrix4.multiplyByVector(transform, negX, west); - west = Cartesian3.fromCartesian4(west).normalize(west); - var angle = computeRotationAngle(west, left, position, ellipsoid); - rotation = Matrix3.fromRotationZ(angle, rotation); - transform = Matrix4.multiply(transform, Matrix4.fromRotationTranslation(rotation, new Cartesian3(0.0, 0.0, height), translation), transform); - for(i = 0; i < shapeLength; i+=3) { - finalPosition = Cartesian3.fromArray(shape, i, finalPosition); - finalPosition = Matrix3.multiplyByVector(scale, finalPosition, finalPosition); - finalPosition = Matrix4.multiplyByPoint(transform, finalPosition, finalPosition); - finalPositions.push(finalPosition.x, finalPosition.y, finalPosition.z); - } + var translation = new Matrix4(); + var rotationMatrix = new Matrix3(); + var scaleMatrix = Matrix3.IDENTITY.clone(); + var westScratch = new Cartesian3(); + var finalPosScratch = new Cartesian3(); + var heightCartesian = new Cartesian3(); + function addPosition(center, left, shape, finalPositions, ellipsoid, height, xScalar) { + var west = westScratch; + var finalPosition = finalPosScratch; + + transform = Transforms.eastNorthUpToFixedFrame(center, ellipsoid, transform); + west = Matrix4.multiplyByVector(transform, negativeX, west); + west = Cartesian3.fromCartesian4(west, west); + west = Cartesian3.normalize(west, west); + var angle = PolylineVolumeGeometryLibrary.computeRotationAngle(west, left, center, ellipsoid); + var rotation = Matrix3.fromRotationZ(angle, rotationMatrix); + heightCartesian.z = height; + transform = Matrix4.multiply(transform, Matrix4.fromRotationTranslation(rotation, heightCartesian, translation), transform); + var scale = scaleMatrix; + scale[0] = xScalar; + + for ( var i = 0; i < shape.length; i += 3) { + finalPosition = Cartesian3.fromArray(shape, i, finalPosition); + finalPosition = Matrix3.multiplyByVector(scale, finalPosition, finalPosition); + finalPosition = Matrix4.multiplyByPoint(transform, finalPosition, finalPosition); + finalPositions.push(finalPosition.x, finalPosition.y, finalPosition.z); + } + + return finalPositions; + } + + var centerScratch = new Cartesian3(); + function addPositions(centers, left, shape, finalPositions, ellipsoid, height, xScalar) { + for ( var i = 0; i < centers.length; i += 3) { + var center = Cartesian3.fromArray(centers, i, centerScratch); + finalPositions = addPosition(center, left, shape, finalPositions, ellipsoid, height, xScalar); } return finalPositions; } function convertShapeTo3DSides(shape2D, boundingRectangle) { //orientate 2D shape to XZ plane center at (0, 0, 0), duplicate points var length = shape2D.length; - var shape = new Array(length * 3); + var shape = new Array(length * 6); var index = 0; + var xOffset = boundingRectangle.x + boundingRectangle.width / 2; + var yOffset = boundingRectangle.y + boundingRectangle.height / 2; - shape[index++] = shape2D[0].x - (boundingRectangle.x + (boundingRectangle.width / 2)); + var point = shape2D[0]; + shape[index++] = point.x - xOffset; shape[index++] = 0; - shape[index++] = shape2D[0].y - (boundingRectangle.y + (boundingRectangle.height / 2)); - for (var i = 1; i < length; i++) { - shape[index++] = shape2D[i].x - (boundingRectangle.x + (boundingRectangle.width / 2)); + shape[index++] = point.y - yOffset; + for ( var i = 1; i < length; i++) { + point = shape2D[i]; + var x = point.x - xOffset; + var z = point.y - yOffset; + shape[index++] = x; shape[index++] = 0; - shape[index++] = shape2D[i].y - (boundingRectangle.y + (boundingRectangle.height / 2)); + shape[index++] = z; - shape[index++] = shape2D[i].x - (boundingRectangle.x + (boundingRectangle.width / 2)); + shape[index++] = x; shape[index++] = 0; - shape[index++] = shape2D[i].y - (boundingRectangle.y + (boundingRectangle.height / 2)); + shape[index++] = z; } - shape[index++] = shape2D[0].x - (boundingRectangle.x + (boundingRectangle.width / 2)); + point = shape2D[0]; + shape[index++] = point.x - xOffset; shape[index++] = 0; - shape[index++] = shape2D[0].y - (boundingRectangle.y + (boundingRectangle.height / 2)); + shape[index++] = point.y - yOffset; return shape; } - function convertShapeTo3DFirstLast(shape2D, boundingRectangle) { //orientate 2D shape to XZ plane center at (0, 0, 0) var length = shape2D.length; var shape = new Array(length * 3); var index = 0; + var xOffset = boundingRectangle.x + boundingRectangle.width / 2; + var yOffset = boundingRectangle.y + boundingRectangle.height / 2; - for (var i = 0; i < length; i++) { - shape[index++] = shape2D[i].x - (boundingRectangle.x + (boundingRectangle.width / 2)); + for ( var i = 0; i < length; i++) { + shape[index++] = shape2D[i].x - xOffset; shape[index++] = 0; - shape[index++] = shape2D[i].y - (boundingRectangle.y + (boundingRectangle.height / 2)); + shape[index++] = shape2D[i].y - yOffset; } return shape; } - var originScratch = new Cartesian2(); - var nextScratch = new Cartesian2(); - var prevScratch = new Cartesian2(); - function angleIsGreaterThanPi (forward, backward, position, ellipsoid) { - var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid); - var origin = tangentPlane.projectPointOntoPlane(position, originScratch); - var next = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, forward, nextScratch), nextScratch); - var prev = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, backward, prevScratch), prevScratch); - - prev = prev.subtract(origin, prev); - next = next.subtract(origin, next); - - return ((prev.x * next.y) - (prev.y * next.x)) >= 0.0; - } - - function computeRotationAngle (start, end, position, ellipsoid) { - var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid); - var origin = tangentPlane.projectPointOntoPlane(position, originScratch); - var next = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, start, nextScratch), nextScratch); - var prev = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, end, prevScratch), prevScratch); - prev = prev.subtract(origin, prev); - next = next.subtract(origin, next); - - var angle = Cartesian2.angleBetween(next, prev); - - return (((prev.x * next.y) - (prev.y * next.x)) >= 0.0) ? -angle : angle; - } - - var posScratch = new Cartesian3(); - function scaleToSurface (positions, ellipsoid){ - for (var i = 0; i < positions.length; i += 3) { - posScratch = Cartesian3.fromArray(positions, i, posScratch); - posScratch = ellipsoid.scaleToGeodeticSurface(posScratch, posScratch); - positions[i] = posScratch.x; - positions[i + 1] = posScratch.y; - positions[i + 2] = posScratch.z; - } - - return positions; - } - var quaterion = new Quaternion(); + var startPointScratch = new Cartesian3(); var rotMatrix = new Matrix3(); - var scratch3Array = new Array(3); function computeRoundCorner(pivot, startPoint, endPoint, cornerType, leftIsOutside, ellipsoid, finalPositions, shape, height) { - var angle = Cartesian3.angleBetween(startPoint.subtract(pivot, scratch1), endPoint.subtract(pivot, scratch2)); - var granularity = (cornerType.value === CornerType.BEVELED.value) ? 0 : Math.ceil(angle/CesiumMath.toRadians(5)); + var angle = Cartesian3.angleBetween(Cartesian3.subtract(startPoint, pivot, scratch1), Cartesian3.subtract(endPoint, pivot, scratch2)); + var granularity = (cornerType.value === CornerType.BEVELED.value) ? 0 : Math.ceil(angle / CesiumMath.toRadians(5)); var m; if (leftIsOutside) { - m = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(pivot, angle/(granularity+1), quaterion), rotMatrix); + m = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(pivot, angle / (granularity + 1), quaterion), rotMatrix); } else { - m = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(pivot.negate(scratch1), angle/(granularity+1), quaterion), rotMatrix); + m = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(Cartesian3.negate(pivot, scratch1), angle / (granularity + 1), quaterion), rotMatrix); } - startPoint = startPoint.clone(scratch1); - for (var i = 0; i < granularity; i++) { - startPoint = m.multiplyByVector(startPoint, startPoint); - scratch3Array[0] = startPoint.x; - scratch3Array[1] = startPoint.y; - scratch3Array[2] = startPoint.z; - - var left = startPoint.subtract(pivot).normalize(); + startPoint = Cartesian3.clone(startPoint, startPointScratch); + for ( var i = 0; i < granularity; i++) { + startPoint = Matrix3.multiplyByVector(m, startPoint, startPoint); + var left = Cartesian3.subtract(startPoint, pivot, scratch1); + left = Cartesian3.normalize(left, left); if (!leftIsOutside) { - left = left.negate(left); + left = Cartesian3.negate(left, left); } - scratch3Array = scaleToSurface(scratch3Array, ellipsoid); - finalPositions = addPositions(scratch3Array, left, shape, finalPositions, ellipsoid, height, 1); + + var surfacePoint = ellipsoid.scaleToGeodeticSurface(startPoint, scratch2); + finalPositions = addPosition(surfacePoint, left, shape, finalPositions, ellipsoid, height, 1); } return finalPositions; @@ -229,7 +205,7 @@ define([ var shapeLength = shape.length; var indices = []; - var length = positions.length/(shapeLength*6); + var length = positions.length / (shapeLength * 6); var i, j; var LL, UL, UR, LR; var offset = shapeLength * 2; @@ -255,27 +231,29 @@ define([ var lengthSt = 1 / (length - 1); var heightSt = 1 / (boundingRectangle.height); + var heightOffset = boundingRectangle.height / 2; + var s, t; for (i = 0; i < length; i++) { t = i * lengthSt; - s = heightSt * (shape[0].y + boundingRectangle.height/2); + s = heightSt * (shape[0].y + heightOffset); st.push(s, t); - for(j = 1; j < shapeLength; j++) { - s = heightSt * (shape[j].y + boundingRectangle.height/2); + for (j = 1; j < shapeLength; j++) { + s = heightSt * (shape[j].y + heightOffset); st.push(s, t); st.push(s, t); } - s = heightSt * (shape[0].y + boundingRectangle.height/2); + s = heightSt * (shape[0].y + heightOffset); st.push(s, t); } - for(j = 0; j < shapeLength; j++) { + for (j = 0; j < shapeLength; j++) { t = 0; - s = heightSt * (shape[j].y + boundingRectangle.height/2); + s = heightSt * (shape[j].y + heightOffset); st.push(s, t); } - for(j = 0; j < shapeLength; j++) { + for (j = 0; j < shapeLength; j++) { t = (length - 1) * lengthSt; - s = heightSt * (shape[j].y + boundingRectangle.height/2); + s = heightSt * (shape[j].y + heightOffset); st.push(s, t); } @@ -297,10 +275,10 @@ define([ } var geometry = new Geometry({ - attributes: attributes, - indices: IndexDatatype.createTypedArray(positions.length/3, indices), - boundingSphere: BoundingSphere.fromVertices(positions), - primitiveType: PrimitiveType.TRIANGLES + attributes : attributes, + indices : IndexDatatype.createTypedArray(positions.length / 3, indices), + boundingSphere : BoundingSphere.fromVertices(positions), + primitiveType : PrimitiveType.TRIANGLES }); if (vertexFormat.normal) { @@ -320,22 +298,19 @@ define([ var cornerType = geometry._cornerType; var ellipsoid = geometry._ellipsoid; var shape2D = geometry._shape; - var windingOrder = PolygonPipeline.computeWindingOrder2D(shape2D); - if (windingOrder.value === WindingOrder.CLOCKWISE.value) { + if (PolygonPipeline.computeWindingOrder2D(shape2D).value === WindingOrder.CLOCKWISE.value) { shape2D.reverse(); } var boundingRectangle = BoundingRectangle.fromPoints(shape2D, brScratch); var shapeForSides = convertShapeTo3DSides(shape2D, boundingRectangle); var shapeForEnds = convertShapeTo3DFirstLast(shape2D, boundingRectangle); - var height = geometry._height + boundingRectangle.height/2; - var width = boundingRectangle.width/2; + var height = geometry._height + boundingRectangle.height / 2; + var width = boundingRectangle.width / 2; + var length = positions.length; var finalPositions = []; - var centers = []; var ends = []; - var i; - var length = positions.length; var forward = scratchCartesian1; var backward = scratchCartesian2; var cornerDirection = scratchCartesian3; @@ -349,84 +324,82 @@ define([ var position = positions[0]; var nextPosition = positions[1]; surfaceNormal = ellipsoid.geodeticSurfaceNormal(position, surfaceNormal); - forward = Cartesian3.subtract(nextPosition, position, forward).normalize(forward); - left = surfaceNormal.cross(forward, left).normalize(left); - - ends = addPositions([position.x, position.y, position.z], left, shapeForEnds, ends, ellipsoid, height, 1); - previousPosition = position.clone(previousPosition); + forward = Cartesian3.subtract(nextPosition, position, forward); + forward = Cartesian3.normalize(forward, forward); + left = Cartesian3.cross(surfaceNormal, forward, left); + left = Cartesian3.normalize(left, left); + ends = addPosition(position, left, shapeForEnds, ends, ellipsoid, height, 1); + previousPosition = Cartesian3.clone(position, previousPosition); position = nextPosition; - backward = forward.negate(backward); + backward = Cartesian3.negate(forward, backward); var surfacePositions; - - for (i = 1; i < length - 1; i++) { + for ( var i = 1; i < length - 1; i++) { nextPosition = positions[i + 1]; - forward = Cartesian3.subtract(nextPosition, position, forward).normalize(forward); - cornerDirection = forward.add(backward, cornerDirection).normalize(cornerDirection); + forward = Cartesian3.subtract(nextPosition, position, forward); + forward = Cartesian3.normalize(forward, forward); + cornerDirection = Cartesian3.add(forward, backward, cornerDirection); + cornerDirection = Cartesian3.normalize(cornerDirection, cornerDirection); surfaceNormal = ellipsoid.geodeticSurfaceNormal(position, surfaceNormal); - var doCorner = !Cartesian3.equalsEpsilon(cornerDirection.negate(scratch1), surfaceNormal, CesiumMath.EPSILON2); + var doCorner = !Cartesian3.equalsEpsilon(Cartesian3.negate(cornerDirection, scratch1), surfaceNormal, CesiumMath.EPSILON2); if (doCorner) { - cornerDirection = cornerDirection.cross(surfaceNormal, cornerDirection); - cornerDirection = surfaceNormal.cross(cornerDirection, cornerDirection); - var scalar = width / Math.max(0.25, (Cartesian3.cross(cornerDirection, backward, scratch1).magnitude())); - var leftIsOutside = angleIsGreaterThanPi(forward, backward, position, ellipsoid); - cornerDirection = cornerDirection.multiplyByScalar(scalar, cornerDirection, cornerDirection); + cornerDirection = Cartesian3.cross(cornerDirection, surfaceNormal, cornerDirection); + cornerDirection = Cartesian3.cross(surfaceNormal, cornerDirection, cornerDirection); + var scalar = width / Math.max(0.25, (Cartesian3.magnitude(Cartesian3.cross(cornerDirection, backward, scratch1)))); + var leftIsOutside = PolylineVolumeGeometryLibrary.angleIsGreaterThanPi(forward, backward, position, ellipsoid); + cornerDirection = Cartesian3.multiplyByScalar(cornerDirection, scalar, cornerDirection); + var ratio = scalar / width; if (leftIsOutside) { pivot = Cartesian3.add(position, cornerDirection, pivot); - start = pivot.add(left.multiplyByScalar(width, start), start); - scratch2Array[0] = previousPosition.clone(scratch2Array[0]); - scratch2Array[1] = start.clone(scratch2Array[1]); + start = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, width, start), start); + scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]); + scratch2Array[1] = Cartesian3.clone(start, scratch2Array[1]); surfacePositions = PolylinePipeline.scaleToSurface(scratch2Array); finalPositions = addPositions(surfacePositions, left, shapeForSides, finalPositions, ellipsoid, height, 1); - centers = centers.concat(surfacePositions); - left = surfaceNormal.cross(forward, left).normalize(left); - end = pivot.add(left.multiplyByScalar(width, end), end); + left = Cartesian3.cross(surfaceNormal, forward, left); + left = Cartesian3.normalize(left, left); + end = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, width, end), end); if (cornerType.value === CornerType.ROUNDED.value || cornerType.value === CornerType.BEVELED.value) { computeRoundCorner(pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, height); } else { - scratch3Array[0] = position.x; - scratch3Array[1] = position.y; - scratch3Array[2] = position.z; - - finalPositions = addPositions(scratch3Array, cornerDirection.negate(cornerDirection), shapeForSides, finalPositions, ellipsoid, height, scalar/width); + cornerDirection = Cartesian3.negate(cornerDirection, cornerDirection); + finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, height, ratio); + finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, height, ratio); } - previousPosition = end.clone(previousPosition); + previousPosition = Cartesian3.clone(end, previousPosition); } else { pivot = Cartesian3.add(position, cornerDirection, pivot); - start = pivot.add(left.multiplyByScalar(-width, start), start); - scratch2Array[0] = previousPosition.clone(scratch2Array[0]); - scratch2Array[1] = start.clone(scratch2Array[1]); + start = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, -width, start), start); + scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]); + scratch2Array[1] = Cartesian3.clone(start, scratch2Array[1]); surfacePositions = PolylinePipeline.scaleToSurface(scratch2Array, granularity, ellipsoid); finalPositions = addPositions(surfacePositions, left, shapeForSides, finalPositions, ellipsoid, height, 1); - centers = centers.concat(surfacePositions); - left = surfaceNormal.cross(forward, left).normalize(left); - end = pivot.add(left.multiplyByScalar(-width, end), end); + left = Cartesian3.cross(surfaceNormal, forward, left); + left = Cartesian3.normalize(left, left); + end = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, -width, end), end); if (cornerType.value === CornerType.ROUNDED.value || cornerType.value === CornerType.BEVELED.value) { computeRoundCorner(pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, height); } else { - scratch3Array[0] = position.x; - scratch3Array[1] = position.y; - scratch3Array[2] = position.z; - - finalPositions = addPositions(scratch3Array, cornerDirection, shapeForSides, finalPositions, ellipsoid, height, scalar/width); + finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, height, ratio); + finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, height, ratio); } - previousPosition = end.clone(previousPosition); + previousPosition = Cartesian3.clone(end, previousPosition); } - backward = forward.negate(backward); + backward = Cartesian3.negate(forward, backward); } position = nextPosition; } - scratch2Array[0] = previousPosition.clone(scratch2Array[0]); - scratch2Array[1] = position.clone(scratch2Array[1]); - centers = PolylinePipeline.scaleToSurface(scratch2Array, granularity, ellipsoid); - finalPositions = addPositions(centers, left, shapeForSides, finalPositions, ellipsoid, height, 1); - ends = addPositions([position.x, position.y, position.z], left, shapeForEnds, ends, ellipsoid, height, 1); + scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]); + scratch2Array[1] = Cartesian3.clone(position, scratch2Array[1]); + surfacePositions = PolylinePipeline.scaleToSurface(scratch2Array, granularity, ellipsoid); + finalPositions = addPositions(surfacePositions, left, shapeForSides, finalPositions, ellipsoid, height, 1); + ends = addPosition(position, left, shapeForEnds, ends, ellipsoid, height, 1); return computeAttributes(finalPositions, ends, shape2D, boundingRectangle, geometry._vertexFormat, ellipsoid); } /** - * A description of a polyline volume. + * A description of a polyline with a volume (a 2D shape extruded along a polyline). * * @alias PolylineVolumeGeometry * @constructor @@ -445,13 +418,23 @@ define([ * @see PolylineVolumeGeometry#createGeometry * * @example + * function computeCirclePositions(radius) { + * var positions = []; + * var theta = CesiumMath.toRadians(1); + * var posCount = Math.PI*2/theta; + * for (var i = 0; i < posCount; i++) { + * positions.push(new Cartesian2(radius * Math.cos(theta * i), radius * Math.sin(theta * i))); + * } + * return positions; + * } + * * var tube = new PolylineVolumeGeometry({ - * vertexFormat : VertexFormat.POSITION_ONLY, - * positions : ellipsoid.cartographicArrayToCartesianArray([ + * vertexFormat : VertexFormat.POSITION_ONLY, + * polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ * Cartographic.fromDegrees(-72.0, 40.0), * Cartographic.fromDegrees(-70.0, 35.0) * ]), - * width : 100000 + * shapePositions : circlePositions(10000) * }); */ var PolylineVolumeGeometry = function(options) { @@ -462,7 +445,7 @@ define([ } var shape = options.shapePositions; if (!defined(shape)) { - throw new DeveloperError('options.shapePositions is required.'); + throw new DeveloperError('options.shapePositions is required.'); } this._positions = positions; @@ -473,14 +456,13 @@ define([ this._vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT); this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); this._workerName = 'createPolylineVolumeGeometry'; - }; /** - * Computes the geometric representation of a corridor, including its vertices, indices, and a bounding sphere. + * Computes the geometric representation of a polyline with a volume, including its vertices, indices, and a bounding sphere. * @memberof PolylineVolumeGeometry * - * @param {PolylineVolumeGeometry} polylineVolumeGeometry A description of the polylineVolume. + * @param {PolylineVolumeGeometry} polylineVolumeGeometry A description of the polyline volume. * * @returns {Geometry} The computed vertices and indices. * @@ -497,5 +479,4 @@ define([ }; return PolylineVolumeGeometry; - }); \ No newline at end of file diff --git a/Source/Core/PolylineVolumeGeometryLibrary.js b/Source/Core/PolylineVolumeGeometryLibrary.js new file mode 100644 index 000000000000..78efb94c49f5 --- /dev/null +++ b/Source/Core/PolylineVolumeGeometryLibrary.js @@ -0,0 +1,58 @@ +/*global define*/ +define([ + './defined', + './Cartesian2', + './Cartesian3', + './CornerType', + './EllipsoidTangentPlane', + './PolylinePipeline', + './Matrix3', + './Quaternion', + './Math' + ], function( + defined, + Cartesian2, + Cartesian3, + CornerType, + EllipsoidTangentPlane, + PolylinePipeline, + Matrix3, + Quaternion, + CesiumMath) { + "use strict"; + + /** + * @private + */ + var PolylineVolumeGeometryLibrary = {}; + + var originScratch = new Cartesian3(); + var nextScratch = new Cartesian3(); + var prevScratch = new Cartesian3(); + PolylineVolumeGeometryLibrary.angleIsGreaterThanPi = function (forward, backward, position, ellipsoid) { + var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid); + var origin = tangentPlane.projectPointOntoPlane(position, originScratch); + var next = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, forward, nextScratch), nextScratch); + var prev = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, backward, prevScratch), prevScratch); + + prev = Cartesian2.subtract(prev, origin, prev); + next = Cartesian2.subtract(next, origin, next); + + return ((prev.x * next.y) - (prev.y * next.x)) >= 0.0; + }; + + PolylineVolumeGeometryLibrary.computeRotationAngle = function (start, end, position, ellipsoid) { + var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid); + var origin = tangentPlane.projectPointOntoPlane(position, originScratch); + var next = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, start, nextScratch), nextScratch); + var prev = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, end, prevScratch), prevScratch); + prev = Cartesian2.subtract(prev, origin, prev); + next = Cartesian2.subtract(next, origin, next); + + var angle = Cartesian2.angleBetween(next, prev); + + return (((prev.x * next.y) - (prev.y * next.x)) >= 0.0) ? -angle : angle; + }; + + return PolylineVolumeGeometryLibrary; +}); \ No newline at end of file From 535dc68984a54743a2e5504bdd8d03d492d788ac Mon Sep 17 00:00:00 2001 From: hpinkos Date: Thu, 5 Sep 2013 16:15:16 -0400 Subject: [PATCH 04/21] completed outline --- Source/Core/PolylineVolumeGeometry.js | 348 ++++--------------- Source/Core/PolylineVolumeGeometryLibrary.js | 300 +++++++++++++++- Source/Core/PolylineVolumeOutlineGeometry.js | 189 ++++++++++ 3 files changed, 554 insertions(+), 283 deletions(-) create mode 100644 Source/Core/PolylineVolumeOutlineGeometry.js diff --git a/Source/Core/PolylineVolumeGeometry.js b/Source/Core/PolylineVolumeGeometry.js index 874d6e625906..0376459a5ada 100644 --- a/Source/Core/PolylineVolumeGeometry.js +++ b/Source/Core/PolylineVolumeGeometry.js @@ -2,25 +2,18 @@ define([ './defined', './DeveloperError', - './Cartesian2', './Cartesian3', - './Cartesian4', './CornerType', './ComponentDatatype', './Ellipsoid', - './EllipsoidTangentPlane', './Geometry', './GeometryPipeline', './IndexDatatype', './Math', - './Matrix3', - './Matrix4', './PolygonPipeline', './PolylinePipeline', './PolylineVolumeGeometryLibrary', './PrimitiveType', - './Quaternion', - './Transforms', './defaultValue', './BoundingSphere', './BoundingRectangle', @@ -31,25 +24,18 @@ define([ ], function( defined, DeveloperError, - Cartesian2, Cartesian3, - Cartesian4, CornerType, ComponentDatatype, Ellipsoid, - EllipsoidTangentPlane, Geometry, GeometryPipeline, IndexDatatype, CesiumMath, - Matrix3, - Matrix4, PolygonPipeline, PolylinePipeline, PolylineVolumeGeometryLibrary, PrimitiveType, - Quaternion, - Transforms, defaultValue, BoundingSphere, BoundingRectangle, @@ -59,202 +45,92 @@ define([ WindingOrder) { "use strict"; - var scratch2Array = [new Cartesian3(), new Cartesian3()]; - - var scratchCartesian1 = new Cartesian3(); - var scratchCartesian2 = new Cartesian3(); - var scratchCartesian3 = new Cartesian3(); - var scratchCartesian4 = new Cartesian3(); - var scratchCartesian5 = new Cartesian3(); - var scratchCartesian6 = new Cartesian3(); - var scratchCartesian7 = new Cartesian3(); - var scratchCartesian8 = new Cartesian3(); - var scratchCartesian9 = new Cartesian3(); - var scratch1 = new Cartesian3(); var scratch2 = new Cartesian3(); - - var negativeX = Cartesian4.UNIT_X.negate(); - var transform = new Matrix4(); - var translation = new Matrix4(); - var rotationMatrix = new Matrix3(); - var scaleMatrix = Matrix3.IDENTITY.clone(); - var westScratch = new Cartesian3(); - var finalPosScratch = new Cartesian3(); - var heightCartesian = new Cartesian3(); - function addPosition(center, left, shape, finalPositions, ellipsoid, height, xScalar) { - var west = westScratch; - var finalPosition = finalPosScratch; - - transform = Transforms.eastNorthUpToFixedFrame(center, ellipsoid, transform); - west = Matrix4.multiplyByVector(transform, negativeX, west); - west = Cartesian3.fromCartesian4(west, west); - west = Cartesian3.normalize(west, west); - var angle = PolylineVolumeGeometryLibrary.computeRotationAngle(west, left, center, ellipsoid); - var rotation = Matrix3.fromRotationZ(angle, rotationMatrix); - heightCartesian.z = height; - transform = Matrix4.multiply(transform, Matrix4.fromRotationTranslation(rotation, heightCartesian, translation), transform); - var scale = scaleMatrix; - scale[0] = xScalar; - - for ( var i = 0; i < shape.length; i += 3) { - finalPosition = Cartesian3.fromArray(shape, i, finalPosition); - finalPosition = Matrix3.multiplyByVector(scale, finalPosition, finalPosition); - finalPosition = Matrix4.multiplyByPoint(transform, finalPosition, finalPosition); - finalPositions.push(finalPosition.x, finalPosition.y, finalPosition.z); - } - - return finalPositions; - } - - var centerScratch = new Cartesian3(); - function addPositions(centers, left, shape, finalPositions, ellipsoid, height, xScalar) { - for ( var i = 0; i < centers.length; i += 3) { - var center = Cartesian3.fromArray(centers, i, centerScratch); - finalPositions = addPosition(center, left, shape, finalPositions, ellipsoid, height, xScalar); - } - return finalPositions; - } - - function convertShapeTo3DSides(shape2D, boundingRectangle) { //orientate 2D shape to XZ plane center at (0, 0, 0), duplicate points - var length = shape2D.length; - var shape = new Array(length * 6); - var index = 0; - var xOffset = boundingRectangle.x + boundingRectangle.width / 2; - var yOffset = boundingRectangle.y + boundingRectangle.height / 2; - - var point = shape2D[0]; - shape[index++] = point.x - xOffset; - shape[index++] = 0; - shape[index++] = point.y - yOffset; - for ( var i = 1; i < length; i++) { - point = shape2D[i]; - var x = point.x - xOffset; - var z = point.y - yOffset; - shape[index++] = x; - shape[index++] = 0; - shape[index++] = z; - - shape[index++] = x; - shape[index++] = 0; - shape[index++] = z; - } - point = shape2D[0]; - shape[index++] = point.x - xOffset; - shape[index++] = 0; - shape[index++] = point.y - yOffset; - - return shape; - } - - function convertShapeTo3DFirstLast(shape2D, boundingRectangle) { //orientate 2D shape to XZ plane center at (0, 0, 0) - var length = shape2D.length; - var shape = new Array(length * 3); - var index = 0; - var xOffset = boundingRectangle.x + boundingRectangle.width / 2; - var yOffset = boundingRectangle.y + boundingRectangle.height / 2; - - for ( var i = 0; i < length; i++) { - shape[index++] = shape2D[i].x - xOffset; - shape[index++] = 0; - shape[index++] = shape2D[i].y - yOffset; - } - - return shape; - } - - var quaterion = new Quaternion(); - var startPointScratch = new Cartesian3(); - var rotMatrix = new Matrix3(); - function computeRoundCorner(pivot, startPoint, endPoint, cornerType, leftIsOutside, ellipsoid, finalPositions, shape, height) { - var angle = Cartesian3.angleBetween(Cartesian3.subtract(startPoint, pivot, scratch1), Cartesian3.subtract(endPoint, pivot, scratch2)); - var granularity = (cornerType.value === CornerType.BEVELED.value) ? 0 : Math.ceil(angle / CesiumMath.toRadians(5)); - - var m; - if (leftIsOutside) { - m = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(pivot, angle / (granularity + 1), quaterion), rotMatrix); - } else { - m = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(Cartesian3.negate(pivot, scratch1), angle / (granularity + 1), quaterion), rotMatrix); - } - - startPoint = Cartesian3.clone(startPoint, startPointScratch); - for ( var i = 0; i < granularity; i++) { - startPoint = Matrix3.multiplyByVector(m, startPoint, startPoint); - var left = Cartesian3.subtract(startPoint, pivot, scratch1); - left = Cartesian3.normalize(left, left); - if (!leftIsOutside) { - left = Cartesian3.negate(left, left); - } - - var surfacePoint = ellipsoid.scaleToGeodeticSurface(startPoint, scratch2); - finalPositions = addPosition(surfacePoint, left, shape, finalPositions, ellipsoid, height, 1); - } - - return finalPositions; - } - - function computeAttributes(positions, ends, shape, boundingRectangle, vertexFormat, ellipsoid) { + function computeAttributes(combinedPositions, shape, boundingRectangle, vertexFormat, ellipsoid) { var attributes = new GeometryAttributes(); if (vertexFormat.position) { attributes.position = new GeometryAttribute({ componentDatatype : ComponentDatatype.DOUBLE, componentsPerAttribute : 3, - values : new Float64Array(positions.concat(ends)) + values : combinedPositions }); } - var shapeLength = shape.length; - var indices = []; - var length = positions.length / (shapeLength * 6); + var vertexCount = combinedPositions.length/3; + var length = (vertexCount - shapeLength * 2) / (shapeLength * 2); + var firstEndIndices = PolygonPipeline.triangulate(shape); + + var indicesCount = (length - 1) * (shapeLength) * 6 + firstEndIndices.length * 2; + var indices = IndexDatatype.createTypedArray(vertexCount, indicesCount); var i, j; var LL, UL, UR, LR; var offset = shapeLength * 2; + var index = 0; for (i = 0; i < length - 1; i++) { for (j = 0; j < shapeLength - 1; j++) { LL = j * 2 + i * shapeLength * 2; + LR = LL + offset; + if (Cartesian3.equalsEpsilon(Cartesian3.fromArray(combinedPositions, LL, scratch1), Cartesian3.fromArray(combinedPositions, LR, scratch2), CesiumMath.EPSILON5)) { + continue; + } UL = LL + 1; UR = UL + offset; - LR = LL + offset; - indices.push(UL, LL, UR, UR, LL, LR); + indices[index++] = UL; + indices[index++] = LL; + indices[index++] = UR; + indices[index++] = UR; + indices[index++] = LL; + indices[index++] = LR; } LL = shapeLength * 2 - 2 + i * shapeLength * 2; UL = LL + 1; UR = UL + offset; LR = LL + offset; - indices.push(UL, LL, UR, UR, LL, LR); + indices[index++] = UL; + indices[index++] = LL; + indices[index++] = UR; + indices[index++] = UR; + indices[index++] = LL; + indices[index++] = LR; } if (vertexFormat.st) { - var st = []; + var st = new Float32Array(vertexCount*2); var lengthSt = 1 / (length - 1); var heightSt = 1 / (boundingRectangle.height); - var heightOffset = boundingRectangle.height / 2; - var s, t; + var stindex = 0; for (i = 0; i < length; i++) { t = i * lengthSt; s = heightSt * (shape[0].y + heightOffset); - st.push(s, t); + st[stindex++] = s; + st[stindex++] = t; for (j = 1; j < shapeLength; j++) { s = heightSt * (shape[j].y + heightOffset); - st.push(s, t); - st.push(s, t); + st[stindex++] = s; + st[stindex++] = t; + st[stindex++] = s; + st[stindex++] = t; } s = heightSt * (shape[0].y + heightOffset); - st.push(s, t); + st[stindex++] = s; + st[stindex++] = t; } for (j = 0; j < shapeLength; j++) { t = 0; s = heightSt * (shape[j].y + heightOffset); - st.push(s, t); + st[stindex++] = s; + st[stindex++] = t; } for (j = 0; j < shapeLength; j++) { t = (length - 1) * lengthSt; s = heightSt * (shape[j].y + heightOffset); - st.push(s, t); + st[stindex++] = s; + st[stindex++] = t; } attributes.st = new GeometryAttribute({ @@ -264,20 +140,24 @@ define([ }); } - var endOffset = positions.length / 3; - var firstEndIndices = PolygonPipeline.triangulate(shape); + var endOffset = vertexCount - shapeLength * 2; for (i = 0; i < firstEndIndices.length; i += 3) { - var v0 = firstEndIndices[i]; - var v1 = firstEndIndices[i + 1]; - var v2 = firstEndIndices[i + 2]; - - indices.push(v0 + endOffset, v1 + endOffset, v2 + endOffset, v2 + endOffset + shapeLength, v1 + endOffset + shapeLength, v0 + endOffset + shapeLength); + var v0 = firstEndIndices[i] + endOffset; + var v1 = firstEndIndices[i + 1] + endOffset; + var v2 = firstEndIndices[i + 2] + endOffset; + + indices[index++] = v0; + indices[index++] = v1; + indices[index++] = v2; + indices[index++] = v2 + shapeLength; + indices[index++] = v1 + shapeLength; + indices[index++] = v0 + shapeLength; } var geometry = new Geometry({ attributes : attributes, - indices : IndexDatatype.createTypedArray(positions.length / 3, indices), - boundingSphere : BoundingSphere.fromVertices(positions), + indices : indices, + boundingSphere : BoundingSphere.fromVertices(combinedPositions), primitiveType : PrimitiveType.TRIANGLES }); @@ -292,112 +172,6 @@ define([ return geometry; } - var brScratch = new BoundingRectangle(); - function computePositions(positions, geometry) { - var granularity = geometry._granularity; - var cornerType = geometry._cornerType; - var ellipsoid = geometry._ellipsoid; - var shape2D = geometry._shape; - if (PolygonPipeline.computeWindingOrder2D(shape2D).value === WindingOrder.CLOCKWISE.value) { - shape2D.reverse(); - } - - var boundingRectangle = BoundingRectangle.fromPoints(shape2D, brScratch); - var shapeForSides = convertShapeTo3DSides(shape2D, boundingRectangle); - var shapeForEnds = convertShapeTo3DFirstLast(shape2D, boundingRectangle); - var height = geometry._height + boundingRectangle.height / 2; - var width = boundingRectangle.width / 2; - var length = positions.length; - var finalPositions = []; - var ends = []; - - var forward = scratchCartesian1; - var backward = scratchCartesian2; - var cornerDirection = scratchCartesian3; - var surfaceNormal = scratchCartesian4; - var pivot = scratchCartesian5; - var start = scratchCartesian6; - var end = scratchCartesian7; - var left = scratchCartesian8; - var previousPosition = scratchCartesian9; - - var position = positions[0]; - var nextPosition = positions[1]; - surfaceNormal = ellipsoid.geodeticSurfaceNormal(position, surfaceNormal); - forward = Cartesian3.subtract(nextPosition, position, forward); - forward = Cartesian3.normalize(forward, forward); - left = Cartesian3.cross(surfaceNormal, forward, left); - left = Cartesian3.normalize(left, left); - ends = addPosition(position, left, shapeForEnds, ends, ellipsoid, height, 1); - previousPosition = Cartesian3.clone(position, previousPosition); - position = nextPosition; - backward = Cartesian3.negate(forward, backward); - - var surfacePositions; - for ( var i = 1; i < length - 1; i++) { - nextPosition = positions[i + 1]; - forward = Cartesian3.subtract(nextPosition, position, forward); - forward = Cartesian3.normalize(forward, forward); - cornerDirection = Cartesian3.add(forward, backward, cornerDirection); - cornerDirection = Cartesian3.normalize(cornerDirection, cornerDirection); - surfaceNormal = ellipsoid.geodeticSurfaceNormal(position, surfaceNormal); - var doCorner = !Cartesian3.equalsEpsilon(Cartesian3.negate(cornerDirection, scratch1), surfaceNormal, CesiumMath.EPSILON2); - if (doCorner) { - cornerDirection = Cartesian3.cross(cornerDirection, surfaceNormal, cornerDirection); - cornerDirection = Cartesian3.cross(surfaceNormal, cornerDirection, cornerDirection); - var scalar = width / Math.max(0.25, (Cartesian3.magnitude(Cartesian3.cross(cornerDirection, backward, scratch1)))); - var leftIsOutside = PolylineVolumeGeometryLibrary.angleIsGreaterThanPi(forward, backward, position, ellipsoid); - cornerDirection = Cartesian3.multiplyByScalar(cornerDirection, scalar, cornerDirection); - var ratio = scalar / width; - if (leftIsOutside) { - pivot = Cartesian3.add(position, cornerDirection, pivot); - start = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, width, start), start); - scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]); - scratch2Array[1] = Cartesian3.clone(start, scratch2Array[1]); - surfacePositions = PolylinePipeline.scaleToSurface(scratch2Array); - finalPositions = addPositions(surfacePositions, left, shapeForSides, finalPositions, ellipsoid, height, 1); - left = Cartesian3.cross(surfaceNormal, forward, left); - left = Cartesian3.normalize(left, left); - end = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, width, end), end); - if (cornerType.value === CornerType.ROUNDED.value || cornerType.value === CornerType.BEVELED.value) { - computeRoundCorner(pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, height); - } else { - cornerDirection = Cartesian3.negate(cornerDirection, cornerDirection); - finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, height, ratio); - finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, height, ratio); - } - previousPosition = Cartesian3.clone(end, previousPosition); - } else { - pivot = Cartesian3.add(position, cornerDirection, pivot); - start = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, -width, start), start); - scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]); - scratch2Array[1] = Cartesian3.clone(start, scratch2Array[1]); - surfacePositions = PolylinePipeline.scaleToSurface(scratch2Array, granularity, ellipsoid); - finalPositions = addPositions(surfacePositions, left, shapeForSides, finalPositions, ellipsoid, height, 1); - left = Cartesian3.cross(surfaceNormal, forward, left); - left = Cartesian3.normalize(left, left); - end = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, -width, end), end); - if (cornerType.value === CornerType.ROUNDED.value || cornerType.value === CornerType.BEVELED.value) { - computeRoundCorner(pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, height); - } else { - finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, height, ratio); - finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, height, ratio); - } - previousPosition = Cartesian3.clone(end, previousPosition); - } - backward = Cartesian3.negate(forward, backward); - } - position = nextPosition; - } - scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]); - scratch2Array[1] = Cartesian3.clone(position, scratch2Array[1]); - surfacePositions = PolylinePipeline.scaleToSurface(scratch2Array, granularity, ellipsoid); - finalPositions = addPositions(surfacePositions, left, shapeForSides, finalPositions, ellipsoid, height, 1); - ends = addPosition(position, left, shapeForEnds, ends, ellipsoid, height, 1); - - return computeAttributes(finalPositions, ends, shape2D, boundingRectangle, geometry._vertexFormat, ellipsoid); - } - /** * A description of a polyline with a volume (a 2D shape extruded along a polyline). * @@ -466,16 +240,28 @@ define([ * * @returns {Geometry} The computed vertices and indices. * - * @exception {DeveloperError} Count of unique positions must be greater than 1. + * @exception {DeveloperError} Count of unique polyline positions must be greater than 1. + * @exception {DeveloperError} Count of unique shape positions must be at least 3. */ + var brScratch = new BoundingRectangle(); PolylineVolumeGeometry.createGeometry = function(polylineVolumeGeometry) { var positions = polylineVolumeGeometry._positions; var cleanPositions = PolylinePipeline.removeDuplicates(positions); if (cleanPositions.length < 2) { - throw new DeveloperError('Count of unique positions must be greater than 1.'); + throw new DeveloperError('Count of unique polyline positions must be greater than 1.'); + } + var shape2D = polylineVolumeGeometry._shape; + shape2D = PolylineVolumeGeometryLibrary.removeDuplicates(shape2D); + if (shape2D.length < 3) { + throw new DeveloperError('Count of unique shape positions must be at least 3.'); } + if (PolygonPipeline.computeWindingOrder2D(shape2D).value === WindingOrder.CLOCKWISE.value) { + shape2D.reverse(); + } + var boundingRectangle = BoundingRectangle.fromPoints(shape2D, brScratch); - return computePositions(cleanPositions, polylineVolumeGeometry); + var computedPositions = PolylineVolumeGeometryLibrary.computePositions(cleanPositions, shape2D, boundingRectangle, polylineVolumeGeometry, true); + return computeAttributes(computedPositions, shape2D, boundingRectangle, polylineVolumeGeometry._vertexFormat, polylineVolumeGeometry._ellipsoid); }; return PolylineVolumeGeometry; diff --git a/Source/Core/PolylineVolumeGeometryLibrary.js b/Source/Core/PolylineVolumeGeometryLibrary.js index 78efb94c49f5..71a09af1fe2f 100644 --- a/Source/Core/PolylineVolumeGeometryLibrary.js +++ b/Source/Core/PolylineVolumeGeometryLibrary.js @@ -3,28 +3,71 @@ define([ './defined', './Cartesian2', './Cartesian3', + './Cartesian4', './CornerType', + './DeveloperError', './EllipsoidTangentPlane', './PolylinePipeline', './Matrix3', + './Matrix4', './Quaternion', + './Transforms', './Math' ], function( defined, Cartesian2, Cartesian3, + Cartesian4, CornerType, + DeveloperError, EllipsoidTangentPlane, PolylinePipeline, Matrix3, + Matrix4, Quaternion, + Transforms, CesiumMath) { "use strict"; + var scratch2Array = [new Cartesian3(), new Cartesian3()]; + var scratchCartesian1 = new Cartesian3(); + var scratchCartesian2 = new Cartesian3(); + var scratchCartesian3 = new Cartesian3(); + var scratchCartesian4 = new Cartesian3(); + var scratchCartesian5 = new Cartesian3(); + var scratchCartesian6 = new Cartesian3(); + var scratchCartesian7 = new Cartesian3(); + var scratchCartesian8 = new Cartesian3(); + var scratchCartesian9 = new Cartesian3(); + + var scratch1 = new Cartesian3(); + var scratch2 = new Cartesian3(); + + var PolylineVolumeGeometryLibrary = {}; + /** * @private */ - var PolylineVolumeGeometryLibrary = {}; + PolylineVolumeGeometryLibrary.removeDuplicates = function(shapePositions) { + if (!defined(shapePositions)) { + throw new DeveloperError('shapePositions is required.'); + } + + var length = shapePositions.length; + + var cleanedPositions = []; + + for ( var i0 = length - 1, i1 = 0; i1 < length; i0 = i1++) { + var v0 = shapePositions[i0]; + var v1 = shapePositions[i1]; + + if (!Cartesian2.equals(v0, v1)) { + cleanedPositions.push(v1); // Shallow copy! + } + } + + return cleanedPositions; + }; var originScratch = new Cartesian3(); var nextScratch = new Cartesian3(); @@ -41,7 +84,7 @@ define([ return ((prev.x * next.y) - (prev.y * next.x)) >= 0.0; }; - PolylineVolumeGeometryLibrary.computeRotationAngle = function (start, end, position, ellipsoid) { + function computeRotationAngle (start, end, position, ellipsoid) { var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid); var origin = tangentPlane.projectPointOntoPlane(position, originScratch); var next = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, start, nextScratch), nextScratch); @@ -52,6 +95,259 @@ define([ var angle = Cartesian2.angleBetween(next, prev); return (((prev.x * next.y) - (prev.y * next.x)) >= 0.0) ? -angle : angle; + } + + var negativeX = Cartesian4.UNIT_X.negate(); + var transform = new Matrix4(); + var translation = new Matrix4(); + var rotationMatrix = new Matrix3(); + var scaleMatrix = Matrix3.IDENTITY.clone(); + var westScratch = new Cartesian3(); + var finalPosScratch = new Cartesian3(); + var heightCartesian = new Cartesian3(); + function addPosition(center, left, shape, finalPositions, ellipsoid, height, xScalar, repeat) { + var west = westScratch; + var finalPosition = finalPosScratch; + + transform = Transforms.eastNorthUpToFixedFrame(center, ellipsoid, transform); + west = Matrix4.multiplyByVector(transform, negativeX, west); + west = Cartesian3.fromCartesian4(west, west); + west = Cartesian3.normalize(west, west); + var angle = computeRotationAngle(west, left, center, ellipsoid); + var rotation = Matrix3.fromRotationZ(angle, rotationMatrix); + heightCartesian.z = height; + transform = Matrix4.multiply(transform, Matrix4.fromRotationTranslation(rotation, heightCartesian, translation), transform); + var scale = scaleMatrix; + scale[0] = xScalar; + + for (var j = 0; j < repeat; j++) { + for ( var i = 0; i < shape.length; i += 3) { + finalPosition = Cartesian3.fromArray(shape, i, finalPosition); + finalPosition = Matrix3.multiplyByVector(scale, finalPosition, finalPosition); + finalPosition = Matrix4.multiplyByPoint(transform, finalPosition, finalPosition); + finalPositions.push(finalPosition.x, finalPosition.y, finalPosition.z); + } + } + + return finalPositions; + } + + var centerScratch = new Cartesian3(); + function addPositions(centers, left, shape, finalPositions, ellipsoid, height, xScalar) { + for ( var i = 0; i < centers.length; i += 3) { + var center = Cartesian3.fromArray(centers, i, centerScratch); + finalPositions = addPosition(center, left, shape, finalPositions, ellipsoid, height, xScalar, 1); + } + return finalPositions; + } + + function convertShapeTo3DDuplicate(shape2D, boundingRectangle) { //orientate 2D shape to XZ plane center at (0, 0, 0), duplicate points + var length = shape2D.length; + var shape = new Array(length * 6); + var index = 0; + var xOffset = boundingRectangle.x + boundingRectangle.width / 2; + var yOffset = boundingRectangle.y + boundingRectangle.height / 2; + + var point = shape2D[0]; + shape[index++] = point.x - xOffset; + shape[index++] = 0; + shape[index++] = point.y - yOffset; + for ( var i = 1; i < length; i++) { + point = shape2D[i]; + var x = point.x - xOffset; + var z = point.y - yOffset; + shape[index++] = x; + shape[index++] = 0; + shape[index++] = z; + + shape[index++] = x; + shape[index++] = 0; + shape[index++] = z; + } + point = shape2D[0]; + shape[index++] = point.x - xOffset; + shape[index++] = 0; + shape[index++] = point.y - yOffset; + + return shape; + } + + function convertShapeTo3D(shape2D, boundingRectangle) { //orientate 2D shape to XZ plane center at (0, 0, 0) + var length = shape2D.length; + var shape = new Array(length * 3); + var index = 0; + var xOffset = boundingRectangle.x + boundingRectangle.width / 2; + var yOffset = boundingRectangle.y + boundingRectangle.height / 2; + + for ( var i = 0; i < length; i++) { + shape[index++] = shape2D[i].x - xOffset; + shape[index++] = 0; + shape[index++] = shape2D[i].y - yOffset; + } + + return shape; + } + + var quaterion = new Quaternion(); + var startPointScratch = new Cartesian3(); + var rotMatrix = new Matrix3(); + function computeRoundCorner(pivot, startPoint, endPoint, cornerType, leftIsOutside, ellipsoid, finalPositions, shape, height, duplicatePoints) { + var angle = Cartesian3.angleBetween(Cartesian3.subtract(startPoint, pivot, scratch1), Cartesian3.subtract(endPoint, pivot, scratch2)); + var granularity = (cornerType.value === CornerType.BEVELED.value) ? 0 : Math.ceil(angle / CesiumMath.toRadians(5)); + + var m; + if (leftIsOutside) { + m = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(pivot, angle / (granularity + 1), quaterion), rotMatrix); + } else { + m = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(Cartesian3.negate(pivot, scratch1), angle / (granularity + 1), quaterion), rotMatrix); + } + + var left; + if (granularity > 0) { + var repeat = duplicatePoints ? 2 : 1; + for ( var i = 0; i < granularity; i++) { + startPoint = Matrix3.multiplyByVector(m, startPoint, startPoint); + left = Cartesian3.subtract(startPoint, pivot, scratch1); + left = Cartesian3.normalize(left, left); + if (!leftIsOutside) { + left = Cartesian3.negate(left, left); + } + var surfacePoint = ellipsoid.scaleToGeodeticSurface(startPoint, scratch2); + finalPositions = addPosition(surfacePoint, left, shape, finalPositions, ellipsoid, height, 1, repeat); + } + } else { + startPoint = Cartesian3.clone(startPoint, startPointScratch); + left = Cartesian3.subtract(startPoint, pivot, scratch1); + left = Cartesian3.normalize(left, left); + if (!leftIsOutside) { + left = Cartesian3.negate(left, left); + } + finalPositions = addPosition(startPoint, left, shape, finalPositions, ellipsoid, height, 1, 1); + + endPoint = Cartesian3.clone(endPoint, startPointScratch); + left = Cartesian3.subtract(endPoint, pivot, scratch1); + left = Cartesian3.normalize(left, left); + if (!leftIsOutside) { + left = Cartesian3.negate(left, left); + } + finalPositions = addPosition(endPoint, left, shape, finalPositions, ellipsoid, height, 1, 1); + } + + return finalPositions; + } + + /** + * @private + */ + PolylineVolumeGeometryLibrary.computePositions = function (positions, shape2D, boundingRectangle, geometry, duplicatePoints) { + var granularity = geometry._granularity; + var cornerType = geometry._cornerType; + var ellipsoid = geometry._ellipsoid; + var shapeForSides = duplicatePoints ? convertShapeTo3DDuplicate(shape2D, boundingRectangle) : convertShapeTo3D(shape2D, boundingRectangle); + var shapeForEnds = duplicatePoints ? convertShapeTo3D(shape2D, boundingRectangle) : undefined; + var height = geometry._height + boundingRectangle.height / 2; + var width = boundingRectangle.width / 2; + var length = positions.length; + var finalPositions = []; + var ends = duplicatePoints ? [] : undefined; + + var forward = scratchCartesian1; + var backward = scratchCartesian2; + var cornerDirection = scratchCartesian3; + var surfaceNormal = scratchCartesian4; + var pivot = scratchCartesian5; + var start = scratchCartesian6; + var end = scratchCartesian7; + var left = scratchCartesian8; + var previousPosition = scratchCartesian9; + + var position = positions[0]; + var nextPosition = positions[1]; + surfaceNormal = ellipsoid.geodeticSurfaceNormal(position, surfaceNormal); + forward = Cartesian3.subtract(nextPosition, position, forward); + forward = Cartesian3.normalize(forward, forward); + left = Cartesian3.cross(surfaceNormal, forward, left); + left = Cartesian3.normalize(left, left); + if (duplicatePoints) { + ends = addPosition(position, left, shapeForEnds, ends, ellipsoid, height, 1, 1); + } + previousPosition = Cartesian3.clone(position, previousPosition); + position = nextPosition; + backward = Cartesian3.negate(forward, backward); + + var surfacePositions; + for ( var i = 1; i < length - 1; i++) { + var repeat = duplicatePoints ? 2 : 1; + nextPosition = positions[i + 1]; + forward = Cartesian3.subtract(nextPosition, position, forward); + forward = Cartesian3.normalize(forward, forward); + cornerDirection = Cartesian3.add(forward, backward, cornerDirection); + cornerDirection = Cartesian3.normalize(cornerDirection, cornerDirection); + surfaceNormal = ellipsoid.geodeticSurfaceNormal(position, surfaceNormal); + var doCorner = !Cartesian3.equalsEpsilon(Cartesian3.negate(cornerDirection, scratch1), surfaceNormal, CesiumMath.EPSILON2); + if (doCorner) { + cornerDirection = Cartesian3.cross(cornerDirection, surfaceNormal, cornerDirection); + cornerDirection = Cartesian3.cross(surfaceNormal, cornerDirection, cornerDirection); + var scalar = width / Math.max(0.25, (Cartesian3.magnitude(Cartesian3.cross(cornerDirection, backward, scratch1)))); + var leftIsOutside = PolylineVolumeGeometryLibrary.angleIsGreaterThanPi(forward, backward, position, ellipsoid); + cornerDirection = Cartesian3.multiplyByScalar(cornerDirection, scalar, cornerDirection); + var ratio = scalar / width; + if (leftIsOutside) { + pivot = Cartesian3.add(position, cornerDirection, pivot); + start = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, width, start), start); + scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]); + scratch2Array[1] = Cartesian3.clone(start, scratch2Array[1]); + surfacePositions = PolylinePipeline.scaleToSurface(scratch2Array); + finalPositions = addPositions(surfacePositions, left, shapeForSides, finalPositions, ellipsoid, height, 1); + left = Cartesian3.cross(surfaceNormal, forward, left); + left = Cartesian3.normalize(left, left); + end = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, width, end), end); + if (cornerType.value === CornerType.ROUNDED.value || cornerType.value === CornerType.BEVELED.value) { + computeRoundCorner(pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, height, duplicatePoints); + } else { + cornerDirection = Cartesian3.negate(cornerDirection, cornerDirection); + finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, height, ratio, repeat); + } + previousPosition = Cartesian3.clone(end, previousPosition); + } else { + pivot = Cartesian3.add(position, cornerDirection, pivot); + start = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, -width, start), start); + scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]); + scratch2Array[1] = Cartesian3.clone(start, scratch2Array[1]); + surfacePositions = PolylinePipeline.scaleToSurface(scratch2Array, granularity, ellipsoid); + finalPositions = addPositions(surfacePositions, left, shapeForSides, finalPositions, ellipsoid, height, 1); + left = Cartesian3.cross(surfaceNormal, forward, left); + left = Cartesian3.normalize(left, left); + end = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, -width, end), end); + if (cornerType.value === CornerType.ROUNDED.value || cornerType.value === CornerType.BEVELED.value) { + computeRoundCorner(pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, height, duplicatePoints); + } else { + finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, height, ratio, repeat); + } + previousPosition = Cartesian3.clone(end, previousPosition); + } + backward = Cartesian3.negate(forward, backward); + } + position = nextPosition; + } + scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]); + scratch2Array[1] = Cartesian3.clone(position, scratch2Array[1]); + surfacePositions = PolylinePipeline.scaleToSurface(scratch2Array, granularity, ellipsoid); + finalPositions = addPositions(surfacePositions, left, shapeForSides, finalPositions, ellipsoid, height, 1); + if (duplicatePoints) { + ends = addPosition(position, left, shapeForEnds, ends, ellipsoid, height, 1, 1); + } + + length = finalPositions.length; + var posLength = duplicatePoints ? length + ends.length : length; + var combinedPositions = new Float64Array(posLength); + combinedPositions.set(finalPositions); + if (duplicatePoints) { + combinedPositions.set(ends, length); + } + + + return combinedPositions; }; return PolylineVolumeGeometryLibrary; diff --git a/Source/Core/PolylineVolumeOutlineGeometry.js b/Source/Core/PolylineVolumeOutlineGeometry.js new file mode 100644 index 000000000000..8244627cbd26 --- /dev/null +++ b/Source/Core/PolylineVolumeOutlineGeometry.js @@ -0,0 +1,189 @@ +/*global define*/ +define([ + './defined', + './DeveloperError', + './Cartesian3', + './CornerType', + './ComponentDatatype', + './Ellipsoid', + './Geometry', + './GeometryPipeline', + './IndexDatatype', + './Math', + './PolygonPipeline', + './PolylinePipeline', + './PolylineVolumeGeometryLibrary', + './PrimitiveType', + './defaultValue', + './BoundingSphere', + './BoundingRectangle', + './GeometryAttribute', + './GeometryAttributes', + './WindingOrder' + ], function( + defined, + DeveloperError, + Cartesian3, + CornerType, + ComponentDatatype, + Ellipsoid, + Geometry, + GeometryPipeline, + IndexDatatype, + CesiumMath, + PolygonPipeline, + PolylinePipeline, + PolylineVolumeGeometryLibrary, + PrimitiveType, + defaultValue, + BoundingSphere, + BoundingRectangle, + GeometryAttribute, + GeometryAttributes, + WindingOrder) { + "use strict"; + + function computeAttributes(positions, shape, boundingRectangle, ellipsoid) { + var attributes = new GeometryAttributes(); + attributes.position = new GeometryAttribute({ + componentDatatype : ComponentDatatype.DOUBLE, + componentsPerAttribute : 3, + values : positions + }); + + var shapeLength = shape.length; + var vertexCount = attributes.position.values.length/3; + var positionLength = positions.length/3; + var shapeCount = positionLength/shapeLength; + var indices = IndexDatatype.createTypedArray(vertexCount, 2 * shapeLength * (shapeCount + 1)); + var i, j; + var index = 0; + i = 0; + var offset = i * shapeLength; + for (j = 0; j < shapeLength - 1; j++) { + indices[index++] = j + offset; + indices[index++] = j + offset + 1; + } + indices[index++] = shapeLength - 1 + offset; + indices[index++] = offset; + + + i = shapeCount - 1; + offset = i * shapeLength; + for (j = 0; j < shapeLength - 1; j++) { + indices[index++] = j + offset; + indices[index++] = j + offset + 1; + } + indices[index++] = shapeLength - 1 + offset; + indices[index++] = offset; + + for (i = 0; i < shapeCount - 1; i++) { + var firstOffset = shapeLength * i; + var secondOffset = firstOffset + shapeLength; + for (j = 0; j < shapeLength; j++) { + indices[index++] = j + firstOffset; + indices[index++] = j + secondOffset; + } + } + + var geometry = new Geometry({ + attributes : attributes, + indices : IndexDatatype.createTypedArray(vertexCount, indices), + boundingSphere : BoundingSphere.fromVertices(positions), + primitiveType : PrimitiveType.LINES + }); + + return geometry; + } + + /** + * A description of a polyline with a volume (a 2D shape extruded along a polyline). + * + * @alias PolylineVolumeOutlineGeometry + * @constructor + * + * @param {Array} options.polylinePositions An array of {Cartesain3} positions that define the center of the polyline volume. + * @param {Number} options.shapePositions An array of {Cartesian2} positions that define the shape to be extruded along the polyline + * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference. + * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer. + * @param {Number} [options.height=0] The distance between the ellipsoid surface and the positions. + * @param {Boolean} [options.cornerType = CornerType.ROUNDED] Determines the style of the corners. + * + * @exception {DeveloperError} options.positions is required. + * @exception {DeveloperError} options.width is required. + * + * @see PolylineVolumeOutlineGeometry#createGeometry + * + * @example + * function computeCirclePositions(radius) { + * var positions = []; + * var theta = CesiumMath.toRadians(1); + * var posCount = Math.PI*2/theta; + * for (var i = 0; i < posCount; i++) { + * positions.push(new Cartesian2(radius * Math.cos(theta * i), radius * Math.sin(theta * i))); + * } + * return positions; + * } + * + * var tubeOutline = new PolylineVolumeOutlineGeometry({ + * polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + * Cartographic.fromDegrees(-72.0, 40.0), + * Cartographic.fromDegrees(-70.0, 35.0) + * ]), + * shapePositions : circlePositions(10000) + * }); + */ + var PolylineVolumeOutlineGeometry = function(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + var positions = options.polylinePositions; + if (!defined(positions)) { + throw new DeveloperError('options.polylinePositions is required.'); + } + var shape = options.shapePositions; + if (!defined(shape)) { + throw new DeveloperError('options.shapePositions is required.'); + } + + this._positions = positions; + this._shape = shape; + this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); + this._height = defaultValue(options.height, 0); + this._cornerType = defaultValue(options.cornerType, CornerType.ROUNDED); + this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); + this._workerName = 'createPolylineVolumeOutlineGeometry'; + }; + + /** + * Computes the geometric representation of the outline of a polyline with a volume, including its vertices, indices, and a bounding sphere. + * @memberof PolylineVolumeOutlineGeometry + * + * @param {PolylineVolumeOutlineGeometry} polylineVolumeOutlineGeometry A description of the polyline volume outline. + * + * @returns {Geometry} The computed vertices and indices. + * + * @exception {DeveloperError} Count of unique polyline positions must be greater than 1. + * @exception {DeveloperError} Count of unique shape positions must be at least 3. + */ + var brScratch = new BoundingRectangle(); + PolylineVolumeOutlineGeometry.createGeometry = function(polylineVolumeOutlineGeometry) { + var positions = polylineVolumeOutlineGeometry._positions; + var cleanPositions = PolylinePipeline.removeDuplicates(positions); + if (cleanPositions.length < 2) { + throw new DeveloperError('Count of unique polyline positions must be greater than 1.'); + } + var shape2D = polylineVolumeOutlineGeometry._shape; + shape2D = PolylineVolumeGeometryLibrary.removeDuplicates(shape2D); + if (shape2D.length < 3) { + throw new DeveloperError('Count of unique shape positions must be at least 3.'); + } + if (PolygonPipeline.computeWindingOrder2D(shape2D).value === WindingOrder.CLOCKWISE.value) { + shape2D.reverse(); + } + var boundingRectangle = BoundingRectangle.fromPoints(shape2D, brScratch); + + var computedPositions = PolylineVolumeGeometryLibrary.computePositions(cleanPositions, shape2D, boundingRectangle, polylineVolumeOutlineGeometry, false); + return computeAttributes(computedPositions, shape2D, boundingRectangle, polylineVolumeOutlineGeometry._ellipsoid); + }; + + return PolylineVolumeOutlineGeometry; +}); \ No newline at end of file From ca6c5cea163cd7b7dce669db67a3ff020cf72892 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Thu, 5 Sep 2013 16:38:56 -0400 Subject: [PATCH 05/21] sandcastle example, web workers --- .../gallery/Geometry and Appearances.html | 73 +++++++++++++++++++ Source/Core/PolygonPipeline.js | 18 ++--- .../Workers/createPolylineVolumeGeometry.js | 28 +++++++ .../createPolylineVolumeOutlineGeometry.js | 28 +++++++ 4 files changed, 138 insertions(+), 9 deletions(-) create mode 100644 Source/Workers/createPolylineVolumeGeometry.js create mode 100644 Source/Workers/createPolylineVolumeOutlineGeometry.js diff --git a/Apps/Sandcastle/gallery/Geometry and Appearances.html b/Apps/Sandcastle/gallery/Geometry and Appearances.html index c2bb668e7846..a57bafe38b36 100644 --- a/Apps/Sandcastle/gallery/Geometry and Appearances.html +++ b/Apps/Sandcastle/gallery/Geometry and Appearances.html @@ -853,6 +853,79 @@ } }) })); + + + function starPositions(arms, rOuter, rInner) { + var angle = Math.PI / arms; + var pos = []; + for (var i = 0; i < 2 * arms; i++) { + var r = (i % 2) === 0 ? rOuter : rInner; + var p = new Cesium.Cartesian2(Math.cos(i * angle) * r, Math.sin(i * angle) * r); + pos.push(p); + } + return pos; + } + + positions = ellipsoid.cartographicArrayToCartesianArray([ + Cesium.Cartographic.fromDegrees(-90.0, 10.0), + Cesium.Cartographic.fromDegrees(-95.0, 15.0), + Cesium.Cartographic.fromDegrees(-100.0, 15.0) + ]); + + var tube = new Cesium.GeometryInstance({ + geometry: new Cesium.PolylineVolumeGeometry({ + polylinePositions : positions, + vertexFormat: Cesium.VertexFormat.ALL, + shapePositions: starPositions(7, 30000, 20000) + }), + attributes: { + color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 1.0})) + } + }); + height = 300000; + var tubeFill = new Cesium.GeometryInstance({ + geometry: new Cesium.PolylineVolumeGeometry({ + polylinePositions : positions, + vertexFormat: Cesium.VertexFormat.ALL, + shapePositions: starPositions(7, 30000, 20000), + height: height + }), + attributes: { + color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 1.0})) + } + }); + + var tubeOutline = new Cesium.GeometryInstance({ + geometry: new Cesium.PolylineVolumeOutlineGeometry({ + polylinePositions : positions, + shapePositions: starPositions(7, 30000, 20000), + height: height + }), + attributes: { + color : solidWhite + } + }); + + primitives.add(new Cesium.Primitive({ + geometryInstances: [tube, tubeFill], + appearance : new Cesium.PerInstanceColorAppearance({ + translucent : false, + closed : true + }) + })); + + primitives.add(new Cesium.Primitive({ + geometryInstances : tubeOutline, + appearance : new Cesium.PerInstanceColorAppearance({ + flat : true, + renderState : { + depthTest : { + enabled : true + }, + lineWidth : Math.min(1.0, scene.getContext().getMaximumAliasedLineWidth()) + } + }) + })); Sandcastle.finishedLoading(); }); diff --git a/Source/Core/PolygonPipeline.js b/Source/Core/PolygonPipeline.js index 18a7e7e9d2a2..0d5b6fbcfcc6 100644 --- a/Source/Core/PolygonPipeline.js +++ b/Source/Core/PolygonPipeline.js @@ -358,7 +358,7 @@ define([ function cleanCut(a1i, a2i, pArray) { return (internalCut(a1i, a2i, pArray) && internalCut(a2i, a1i, pArray)) && !intersectsSide(pArray[a1i].position, pArray[a2i].position, pArray) && - !pArray[a1i].position.equals(pArray[a2i].position); + !Cartesian2.equals(pArray[a1i].position, pArray[a2i].position); } /** @@ -386,9 +386,9 @@ define([ var before = getNextVertex(a1i, pArray, BEFORE); var after = getNextVertex(a1i, pArray, AFTER); - var s1 = pArray[before].position.subtract(a1.position); - var s2 = pArray[after].position.subtract(a1.position); - var cut = a2.position.subtract(a1.position); + var s1 = Cartesian2.subtract(pArray[before].position, a1.position); + var s2 = Cartesian2.subtract(pArray[after].position, a1.position); + var cut = Cartesian2.subtract(a2.position, a1.position); // Convert to 3-dimensional so we can use cross product s1 = new Cartesian3(s1.x, s1.y, 0.0); @@ -514,8 +514,8 @@ define([ after = 0; } - var s1 = pArray[before].position.subtract(pArray[index].position); - var s2 = pArray[after].position.subtract(pArray[index].position); + var s1 = Cartesian2.subtract(pArray[before].position, pArray[index].position); + var s2 = Cartesian2.subtract(pArray[after].position, pArray[index].position); // Convert to 3-dimensional so we can use cross product s1 = new Cartesian3(s1.x, s1.y, 0.0); @@ -624,7 +624,7 @@ define([ } // If there's a duplicate point, there's no intersection here. - if (a1.equals(b1) || a2.equals(b2) || a1.equals(b2) || a2.equals(b1)) { + if (Cartesian2.equals(a1, b1) || Cartesian2.equals(a2, b2) || Cartesian2.equals(a1, b2) || Cartesian2.equals(a2, b1)) { continue; } @@ -672,8 +672,8 @@ define([ var v2 = pArray[1].position; var v3 = pArray[2].position; - var side1 = v2.subtract(v1); - var side2 = v3.subtract(v1); + var side1 = Cartesian2.subtract(v2, v1); + var side2 = Cartesian2.subtract(v3, v1); // Convert to 3-dimensional so we can use cross product side1 = new Cartesian3(side1.x, side1.y, 0.0); diff --git a/Source/Workers/createPolylineVolumeGeometry.js b/Source/Workers/createPolylineVolumeGeometry.js new file mode 100644 index 000000000000..68bbe1bf4e83 --- /dev/null +++ b/Source/Workers/createPolylineVolumeGeometry.js @@ -0,0 +1,28 @@ +/*global define*/ +define([ + '../Core/PolylineVolumeGeometry', + '../Core/Ellipsoid', + '../Scene/PrimitivePipeline', + './createTaskProcessorWorker' + ], function( + PolylineVolumeGeometry, + Ellipsoid, + PrimitivePipeline, + createTaskProcessorWorker) { + "use strict"; + + function createPolylineVolumeGeometry(parameters, transferableObjects) { + var polylineVolumeGeometry = parameters.geometry; + polylineVolumeGeometry._ellipsoid = Ellipsoid.clone(polylineVolumeGeometry._ellipsoid); + + var geometry = PolylineVolumeGeometry.createGeometry(polylineVolumeGeometry); + PrimitivePipeline.transferGeometry(geometry, transferableObjects); + + return { + geometry : geometry, + index : parameters.index + }; + } + + return createTaskProcessorWorker(createPolylineVolumeGeometry); +}); diff --git a/Source/Workers/createPolylineVolumeOutlineGeometry.js b/Source/Workers/createPolylineVolumeOutlineGeometry.js new file mode 100644 index 000000000000..7d86c89a3dd5 --- /dev/null +++ b/Source/Workers/createPolylineVolumeOutlineGeometry.js @@ -0,0 +1,28 @@ +/*global define*/ +define([ + '../Core/PolylineVolumeOutlineGeometry', + '../Core/Ellipsoid', + '../Scene/PrimitivePipeline', + './createTaskProcessorWorker' + ], function( + PolylineVolumeOutlineGeometry, + Ellipsoid, + PrimitivePipeline, + createTaskProcessorWorker) { + "use strict"; + + function createPolylineVolumeOutlineGeometry(parameters, transferableObjects) { + var polylineVolumeOutlineGeometry = parameters.geometry; + polylineVolumeOutlineGeometry._ellipsoid = Ellipsoid.clone(polylineVolumeOutlineGeometry._ellipsoid); + + var geometry = PolylineVolumeOutlineGeometry.createGeometry(polylineVolumeOutlineGeometry); + PrimitivePipeline.transferGeometry(geometry, transferableObjects); + + return { + geometry : geometry, + index : parameters.index + }; + } + + return createTaskProcessorWorker(createPolylineVolumeOutlineGeometry); +}); From e004b0c508f4a06021c9f3f46823a050a59f087f Mon Sep 17 00:00:00 2001 From: hpinkos Date: Thu, 5 Sep 2013 17:46:11 -0400 Subject: [PATCH 06/21] specs --- CHANGES.md | 1 + Source/Core/PolylineVolumeGeometry.js | 6 - Source/Core/PolylineVolumeGeometryLibrary.js | 6 - Specs/Core/PolylineVolumeGeometrySpec.js | 184 ++++++++++++++++++ .../Core/PolylineVolumeOutlineGeometrySpec.js | 156 +++++++++++++++ Specs/Scene/GeometryRenderingSpec.js | 96 +++++++++ 6 files changed, 437 insertions(+), 12 deletions(-) create mode 100644 Specs/Core/PolylineVolumeGeometrySpec.js create mode 100644 Specs/Core/PolylineVolumeOutlineGeometrySpec.js diff --git a/CHANGES.md b/CHANGES.md index 0416e828a01d..59a684b40983 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,7 @@ Beta Releases * Added `CorridorOutlineGeometry`. * Improved runtime generation of GLSL shaders. * Added new built-in GLSL functions `czm_getLambertDiffuse` and `czm_getSpecular`. +* Added `PolylineVolumeGeometry` and `PolylineVolumeGeometryOutline`. ### b20 - 2013-09-03 diff --git a/Source/Core/PolylineVolumeGeometry.js b/Source/Core/PolylineVolumeGeometry.js index 0376459a5ada..73ac2e8525c4 100644 --- a/Source/Core/PolylineVolumeGeometry.js +++ b/Source/Core/PolylineVolumeGeometry.js @@ -44,9 +44,6 @@ define([ VertexFormat, WindingOrder) { "use strict"; - - var scratch1 = new Cartesian3(); - var scratch2 = new Cartesian3(); function computeAttributes(combinedPositions, shape, boundingRectangle, vertexFormat, ellipsoid) { var attributes = new GeometryAttributes(); if (vertexFormat.position) { @@ -71,9 +68,6 @@ define([ for (j = 0; j < shapeLength - 1; j++) { LL = j * 2 + i * shapeLength * 2; LR = LL + offset; - if (Cartesian3.equalsEpsilon(Cartesian3.fromArray(combinedPositions, LL, scratch1), Cartesian3.fromArray(combinedPositions, LR, scratch2), CesiumMath.EPSILON5)) { - continue; - } UL = LL + 1; UR = UL + offset; diff --git a/Source/Core/PolylineVolumeGeometryLibrary.js b/Source/Core/PolylineVolumeGeometryLibrary.js index 71a09af1fe2f..3e1e67bbc8c2 100644 --- a/Source/Core/PolylineVolumeGeometryLibrary.js +++ b/Source/Core/PolylineVolumeGeometryLibrary.js @@ -49,14 +49,8 @@ define([ * @private */ PolylineVolumeGeometryLibrary.removeDuplicates = function(shapePositions) { - if (!defined(shapePositions)) { - throw new DeveloperError('shapePositions is required.'); - } - var length = shapePositions.length; - var cleanedPositions = []; - for ( var i0 = length - 1, i1 = 0; i1 < length; i0 = i1++) { var v0 = shapePositions[i0]; var v1 = shapePositions[i1]; diff --git a/Specs/Core/PolylineVolumeGeometrySpec.js b/Specs/Core/PolylineVolumeGeometrySpec.js new file mode 100644 index 000000000000..9f9a0b1103fc --- /dev/null +++ b/Specs/Core/PolylineVolumeGeometrySpec.js @@ -0,0 +1,184 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/PolylineVolumeGeometry', + 'Core/CornerType', + 'Core/Cartesian2', + 'Core/Cartesian3', + 'Core/Cartographic', + 'Core/Ellipsoid', + 'Core/Math', + 'Core/VertexFormat' + ], function( + PolylineVolumeGeometry, + CornerType, + Cartesian2, + Cartesian3, + Cartographic, + Ellipsoid, + CesiumMath, + VertexFormat) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + var shape; + + beforeAll(function() { + shape = [new Cartesian2(-10000, -10000), new Cartesian2(10000, -10000), new Cartesian2(10000, 10000), new Cartesian2(-10000, 10000)]; + }); + + + it('throws without polyline positions', function() { + expect(function() { + return new PolylineVolumeGeometry({}); + }).toThrow(); + }); + + it('throws without shape positions', function() { + expect(function() { + return new PolylineVolumeGeometry({ + polylinePositions: [new Cartesian3()] + }); + }).toThrow(); + }); + + it('throws without 2 unique polyline positions', function() { + expect(function() { + return PolylineVolumeGeometry.createGeometry(new PolylineVolumeGeometry({ + polylinePositions: [new Cartesian3()], + shapePositions: shape + })); + }).toThrow(); + }); + + it('throws without 3 unique shape positions', function() { + expect(function() { + return PolylineVolumeGeometry.createGeometry(new PolylineVolumeGeometry({ + polylinePositions: [Cartesian3.UNIT_X, Cartesian3.UNIT_Y], + shapePositions: [Cartesian2.UNIT_X, Cartesian2.UNIT_X, Cartesian2.UNIT_X] + })); + }).toThrow(); + }); + + it('computes positions', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeGeometry.createGeometry(new PolylineVolumeGeometry({ + vertexFormat : VertexFormat.POSITION_ONLY, + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -35.0) + ]), + cornerType: CornerType.MITERED, + shapePositions: shape + })); + + expect(m.attributes.position.values.length).toEqual(3 * (4 * 2 + 4 * 2 * 6)); + expect(m.indices.length).toEqual(3 * 10 * 2 + 24 * 3); + }); + + it('computes positions, clockwise shape', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeGeometry.createGeometry(new PolylineVolumeGeometry({ + vertexFormat : VertexFormat.POSITION_ONLY, + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -35.0) + ]), + cornerType: CornerType.MITERED, + shapePositions: shape.reverse() + })); + + expect(m.attributes.position.values.length).toEqual(3 * (4 * 2 + 4 * 2 * 6)); + expect(m.indices.length).toEqual(3 * 10 * 2 + 24 * 3); + }); + + + it('compute all vertex attributes', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeGeometry.createGeometry(new PolylineVolumeGeometry({ + vertexFormat : VertexFormat.ALL, + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -35.0) + ]), + cornerType: CornerType.MITERED, + shapePositions: shape + })); + + expect(m.attributes.position.values.length).toEqual(3 * (4 * 2 + 4 * 2 * 6)); + expect(m.attributes.st.values.length).toEqual(2 * (4 * 2 + 4 * 2 * 6)); + expect(m.attributes.normal.values.length).toEqual(3 * (4 * 2 + 4 * 2 * 6)); + expect(m.attributes.tangent.values.length).toEqual(3 * (4 * 2 + 4 * 2 * 6)); + expect(m.attributes.binormal.values.length).toEqual(3 * (4 * 2 + 4 * 2 * 6)); + expect(m.indices.length).toEqual(3 * 10 * 2 + 24 * 3); + }); + + it('computes right turn', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeGeometry.createGeometry(new PolylineVolumeGeometry({ + vertexFormat : VertexFormat.POSITION_ONLY, + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -31.0), + Cartographic.fromDegrees(91.0, -31.0) + ]), + cornerType: CornerType.MITERED, + shapePositions: shape + })); + + expect(m.attributes.position.values.length).toEqual(3 * (4 * 2 + 4 * 2 * 6)); + expect(m.indices.length).toEqual(3 * 10 * 2 + 24 * 3); + }); + + it('computes left turn', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeGeometry.createGeometry(new PolylineVolumeGeometry({ + vertexFormat : VertexFormat.POSITION_ONLY, + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -31.0), + Cartographic.fromDegrees(89.0, -31.0) + ]), + cornerType: CornerType.MITERED, + shapePositions: shape + })); + + expect(m.attributes.position.values.length).toEqual(3 * (4 * 2 + 4 * 2 * 6)); + expect(m.indices.length).toEqual(3 * 10 * 2 + 24 * 3); + }); + + it('computes with rounded corners', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeGeometry.createGeometry(new PolylineVolumeGeometry({ + vertexFormat : VertexFormat.POSITION_ONLY, + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -31.0), + Cartographic.fromDegrees(89.0, -31.0), + Cartographic.fromDegrees(89.0, -32.0) + ]), + cornerType: CornerType.ROUNDED, + shapePositions: shape + })); + + var corners = 90/5 * 2; + expect(m.attributes.position.values.length).toEqual(3 * (corners * 4 * 2 * 2 + 4 * 2 * 9)); + expect(m.indices.length).toEqual(3 * (corners * 4 * 2 * 2 + 4 * 7 * 2 + 4)); + }); + + it('computes with beveled corners', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeGeometry.createGeometry(new PolylineVolumeGeometry({ + vertexFormat : VertexFormat.POSITION_ONLY, + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -31.0), + Cartographic.fromDegrees(89.0, -31.0), + Cartographic.fromDegrees(89.0, -32.0) + ]), + cornerType: CornerType.BEVELED, + shapePositions: shape + })); + + expect(m.attributes.position.values.length).toEqual(3 * (2 * 8 + 4 * 2 * 9)); + expect(m.indices.length).toEqual(3 * (8 * 2 + 4 * 7 * 2 + 4)); + }); +}); diff --git a/Specs/Core/PolylineVolumeOutlineGeometrySpec.js b/Specs/Core/PolylineVolumeOutlineGeometrySpec.js new file mode 100644 index 000000000000..c21c2a7f912e --- /dev/null +++ b/Specs/Core/PolylineVolumeOutlineGeometrySpec.js @@ -0,0 +1,156 @@ +/*global defineSuite*/ +defineSuite([ + 'Core/PolylineVolumeOutlineGeometry', + 'Core/CornerType', + 'Core/Cartesian2', + 'Core/Cartesian3', + 'Core/Cartographic', + 'Core/Ellipsoid', + 'Core/Math' + ], function( + PolylineVolumeOutlineGeometry, + CornerType, + Cartesian2, + Cartesian3, + Cartographic, + Ellipsoid, + CesiumMath) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + var shape; + + beforeAll(function() { + shape = [new Cartesian2(-10000, -10000), new Cartesian2(10000, -10000), new Cartesian2(10000, 10000), new Cartesian2(-10000, 10000)]; + }); + + + it('throws without polyline positions', function() { + expect(function() { + return new PolylineVolumeOutlineGeometry({}); + }).toThrow(); + }); + + it('throws without shape positions', function() { + expect(function() { + return new PolylineVolumeOutlineGeometry({ + polylinePositions: [new Cartesian3()] + }); + }).toThrow(); + }); + + it('throws without 2 unique polyline positions', function() { + expect(function() { + return PolylineVolumeOutlineGeometry.createGeometry(new PolylineVolumeOutlineGeometry({ + polylinePositions: [new Cartesian3()], + shapePositions: shape + })); + }).toThrow(); + }); + + it('throws without 3 unique shape positions', function() { + expect(function() { + return PolylineVolumeOutlineGeometry.createGeometry(new PolylineVolumeOutlineGeometry({ + polylinePositions: [Cartesian3.UNIT_X, Cartesian3.UNIT_Y], + shapePositions: [Cartesian2.UNIT_X, Cartesian2.UNIT_X, Cartesian2.UNIT_X] + })); + }).toThrow(); + }); + + it('computes positions', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeOutlineGeometry.createGeometry(new PolylineVolumeOutlineGeometry({ + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -35.0) + ]), + shapePositions: shape, + cornerType: CornerType.MITERED + })); + + expect(m.attributes.position.values.length).toEqual(3 * 6 * 4); + expect(m.indices.length).toEqual(2 * 24 + 8); + }); + + it('computes positions, clockwise shape', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeOutlineGeometry.createGeometry(new PolylineVolumeOutlineGeometry({ + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -35.0) + ]), + shapePositions: shape.reverse(), + cornerType: CornerType.MITERED + })); + + expect(m.attributes.position.values.length).toEqual(3 * 6 * 4); + expect(m.indices.length).toEqual(2 * 24 + 8); + }); + + it('computes right turn', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeOutlineGeometry.createGeometry(new PolylineVolumeOutlineGeometry({ + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -31.0), + Cartographic.fromDegrees(91.0, -31.0) + ]), + cornerType: CornerType.MITERED, + shapePositions: shape + })); + + expect(m.attributes.position.values.length).toEqual(3 * 5 * 4); + expect(m.indices.length).toEqual(2 * 24); + }); + + it('computes left turn', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeOutlineGeometry.createGeometry(new PolylineVolumeOutlineGeometry({ + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -31.0), + Cartographic.fromDegrees(89.0, -31.0) + ]), + cornerType: CornerType.MITERED, + shapePositions: shape + })); + + expect(m.attributes.position.values.length).toEqual(3 * 5 * 4); + expect(m.indices.length).toEqual(2 * 24); + }); + + it('computes with rounded corners', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeOutlineGeometry.createGeometry(new PolylineVolumeOutlineGeometry({ + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -31.0), + Cartographic.fromDegrees(89.0, -31.0), + Cartographic.fromDegrees(89.0, -32.0) + ]), + cornerType: CornerType.ROUNDED, + shapePositions: shape + })); + + var corners = 90/5*2; + expect(m.attributes.position.values.length).toEqual(3 * (corners * 4 + 7 * 4)); + expect(m.indices.length).toEqual(2 * (corners * 4 + 6 * 4 + 8)); + }); + + it('computes with beveled corners', function() { + var ellipsoid = Ellipsoid.WGS84; + var m = PolylineVolumeOutlineGeometry.createGeometry(new PolylineVolumeOutlineGeometry({ + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(90.0, -30.0), + Cartographic.fromDegrees(90.0, -31.0), + Cartographic.fromDegrees(89.0, -31.0), + Cartographic.fromDegrees(89.0, -32.0) + ]), + cornerType: CornerType.BEVELED, + shapePositions: shape + })); + + expect(m.attributes.position.values.length).toEqual(3 * 20 * 2); + expect(m.indices.length).toEqual(2 * 20 * 2 + 8); + }); +}); \ No newline at end of file diff --git a/Specs/Scene/GeometryRenderingSpec.js b/Specs/Scene/GeometryRenderingSpec.js index 85cb2c76c904..285889875f10 100644 --- a/Specs/Scene/GeometryRenderingSpec.js +++ b/Specs/Scene/GeometryRenderingSpec.js @@ -20,11 +20,13 @@ defineSuite([ 'Core/ColorGeometryInstanceAttribute', 'Core/GeometryInstanceAttribute', 'Core/ComponentDatatype', + 'Core/Cartesian2', 'Core/Cartesian3', 'Core/Matrix4', 'Core/Extent', 'Core/Ellipsoid', 'Core/PrimitiveType', + 'Core/PolylineVolumeGeometry', 'Core/Transforms', 'Core/Cartographic', 'Core/BoundingSphere', @@ -64,11 +66,13 @@ defineSuite([ ColorGeometryInstanceAttribute, GeometryInstanceAttribute, ComponentDatatype, + Cartesian2, Cartesian3, Matrix4, Extent, Ellipsoid, PrimitiveType, + PolylineVolumeGeometry, Transforms, Cartographic, BoundingSphere, @@ -1230,6 +1234,98 @@ defineSuite([ }); }, 'WebGL'); + describe('PolylineVolumeGeometry', function() { + var instance; + var geometryHeight; + var positions; + var shape; + beforeAll(function() { + positions = ellipsoid.cartographicArrayToCartesianArray([ + Cartographic.fromDegrees(0.0, -1.0), + Cartographic.fromDegrees(0.0, 1.0) + ]); + shape = [new Cartesian2(-100000, -100000), new Cartesian2(100000, -100000), new Cartesian2(100000, 100000), new Cartesian2(-100000, 100000)]; + geometryHeight = 150000.0; + instance = new GeometryInstance({ + geometry : new PolylineVolumeGeometry({ + vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT, + ellipsoid : ellipsoid, + polylinePositions: positions, + shapePositions: shape, + cornerType: CornerType.MITERED, + height: geometryHeight + }), + id : 'polylineVolume', + attributes : { + color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 1.0) + } + }); + }); + + it('3D', function() { + render3D(instance); + }); + + it('Columbus view', function() { + renderCV(instance); + }); + + it('2D', function() { + render2D(instance); + }); + + it('pick', function() { + pickGeometry(instance); + }); + + it('async', function() { + renderAsync(instance); + }); + + it('renders bottom', function() { + var afterView = function(frameState, primitive) { + var height = geometryHeight * 0.5; + var transform = Matrix4.multiplyByTranslation( + Transforms.eastNorthUpToFixedFrame(primitive._boundingSphere.center), + new Cartesian3(0.0, 0.0, height)); + frameState.camera.controller.rotateDown(CesiumMath.PI, transform); + }; + render3D(instance, afterView); + }); + + it('renders north wall', function() { + var afterView = function(frameState, primitive) { + var transform = Transforms.eastNorthUpToFixedFrame(primitive._boundingSphere.center); + frameState.camera.controller.rotateDown(-CesiumMath.PI_OVER_TWO, transform); + }; + render3D(instance, afterView); + }); + + it('renders south wall', function() { + var afterView = function(frameState, primitive) { + var transform = Transforms.eastNorthUpToFixedFrame(primitive._boundingSphere.center); + frameState.camera.controller.rotateDown(CesiumMath.PI_OVER_TWO, transform); + }; + render3D(instance, afterView); + }); + + it('renders west wall', function() { + var afterView = function(frameState, primitive) { + var transform = Transforms.eastNorthUpToFixedFrame(primitive._boundingSphere.center); + frameState.camera.controller.rotateRight(-CesiumMath.PI_OVER_TWO, transform); + }; + render3D(instance, afterView); + }); + + it('renders east wall', function() { + var afterView = function(frameState, primitive) { + var transform = Transforms.eastNorthUpToFixedFrame(primitive._boundingSphere.center); + frameState.camera.controller.rotateRight(CesiumMath.PI_OVER_TWO, transform); + }; + render3D(instance, afterView); + }); + }, 'WebGL'); + describe('SimplePolylineGeometry', function() { var instance; From 75c50ee7b74f657cd466b8b9eb6cfbf4c6e34875 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Thu, 5 Sep 2013 17:49:53 -0400 Subject: [PATCH 07/21] formatting --- Apps/Sandcastle/gallery/Geometry and Appearances.html | 3 +-- Source/Core/PolylineVolumeGeometry.js | 8 +++++--- Source/Core/PolylineVolumeGeometryLibrary.js | 9 ++++----- Source/Core/PolylineVolumeOutlineGeometry.js | 7 +++---- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Apps/Sandcastle/gallery/Geometry and Appearances.html b/Apps/Sandcastle/gallery/Geometry and Appearances.html index a57bafe38b36..7c87a40bbcb4 100644 --- a/Apps/Sandcastle/gallery/Geometry and Appearances.html +++ b/Apps/Sandcastle/gallery/Geometry and Appearances.html @@ -853,7 +853,6 @@ } }) })); - function starPositions(arms, rOuter, rInner) { var angle = Math.PI / arms; @@ -871,7 +870,7 @@ Cesium.Cartographic.fromDegrees(-95.0, 15.0), Cesium.Cartographic.fromDegrees(-100.0, 15.0) ]); - + var tube = new Cesium.GeometryInstance({ geometry: new Cesium.PolylineVolumeGeometry({ polylinePositions : positions, diff --git a/Source/Core/PolylineVolumeGeometry.js b/Source/Core/PolylineVolumeGeometry.js index 73ac2e8525c4..1f85c498d87e 100644 --- a/Source/Core/PolylineVolumeGeometry.js +++ b/Source/Core/PolylineVolumeGeometry.js @@ -44,7 +44,9 @@ define([ VertexFormat, WindingOrder) { "use strict"; - function computeAttributes(combinedPositions, shape, boundingRectangle, vertexFormat, ellipsoid) { + + + function computeAttributes(combinedPositions, shape, boundingRectangle, vertexFormat, ellipsoid) { var attributes = new GeometryAttributes(); if (vertexFormat.position) { attributes.position = new GeometryAttribute({ @@ -54,7 +56,7 @@ define([ }); } var shapeLength = shape.length; - var vertexCount = combinedPositions.length/3; + var vertexCount = combinedPositions.length / 3; var length = (vertexCount - shapeLength * 2) / (shapeLength * 2); var firstEndIndices = PolygonPipeline.triangulate(shape); @@ -92,7 +94,7 @@ define([ } if (vertexFormat.st) { - var st = new Float32Array(vertexCount*2); + var st = new Float32Array(vertexCount * 2); var lengthSt = 1 / (length - 1); var heightSt = 1 / (boundingRectangle.height); var heightOffset = boundingRectangle.height / 2; diff --git a/Source/Core/PolylineVolumeGeometryLibrary.js b/Source/Core/PolylineVolumeGeometryLibrary.js index 3e1e67bbc8c2..825d3c291ed3 100644 --- a/Source/Core/PolylineVolumeGeometryLibrary.js +++ b/Source/Core/PolylineVolumeGeometryLibrary.js @@ -66,7 +66,7 @@ define([ var originScratch = new Cartesian3(); var nextScratch = new Cartesian3(); var prevScratch = new Cartesian3(); - PolylineVolumeGeometryLibrary.angleIsGreaterThanPi = function (forward, backward, position, ellipsoid) { + PolylineVolumeGeometryLibrary.angleIsGreaterThanPi = function(forward, backward, position, ellipsoid) { var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid); var origin = tangentPlane.projectPointOntoPlane(position, originScratch); var next = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, forward, nextScratch), nextScratch); @@ -78,7 +78,7 @@ define([ return ((prev.x * next.y) - (prev.y * next.x)) >= 0.0; }; - function computeRotationAngle (start, end, position, ellipsoid) { + function computeRotationAngle(start, end, position, ellipsoid) { var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid); var origin = tangentPlane.projectPointOntoPlane(position, originScratch); var next = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, start, nextScratch), nextScratch); @@ -114,7 +114,7 @@ define([ var scale = scaleMatrix; scale[0] = xScalar; - for (var j = 0; j < repeat; j++) { + for ( var j = 0; j < repeat; j++) { for ( var i = 0; i < shape.length; i += 3) { finalPosition = Cartesian3.fromArray(shape, i, finalPosition); finalPosition = Matrix3.multiplyByVector(scale, finalPosition, finalPosition); @@ -233,7 +233,7 @@ define([ /** * @private */ - PolylineVolumeGeometryLibrary.computePositions = function (positions, shape2D, boundingRectangle, geometry, duplicatePoints) { + PolylineVolumeGeometryLibrary.computePositions = function(positions, shape2D, boundingRectangle, geometry, duplicatePoints) { var granularity = geometry._granularity; var cornerType = geometry._cornerType; var ellipsoid = geometry._ellipsoid; @@ -340,7 +340,6 @@ define([ combinedPositions.set(ends, length); } - return combinedPositions; }; diff --git a/Source/Core/PolylineVolumeOutlineGeometry.js b/Source/Core/PolylineVolumeOutlineGeometry.js index 8244627cbd26..2d1f303bc990 100644 --- a/Source/Core/PolylineVolumeOutlineGeometry.js +++ b/Source/Core/PolylineVolumeOutlineGeometry.js @@ -52,9 +52,9 @@ define([ }); var shapeLength = shape.length; - var vertexCount = attributes.position.values.length/3; - var positionLength = positions.length/3; - var shapeCount = positionLength/shapeLength; + var vertexCount = attributes.position.values.length / 3; + var positionLength = positions.length / 3; + var shapeCount = positionLength / shapeLength; var indices = IndexDatatype.createTypedArray(vertexCount, 2 * shapeLength * (shapeCount + 1)); var i, j; var index = 0; @@ -67,7 +67,6 @@ define([ indices[index++] = shapeLength - 1 + offset; indices[index++] = offset; - i = shapeCount - 1; offset = i * shapeLength; for (j = 0; j < shapeLength - 1; j++) { From 87022e05239437df00c8378a31d3f18925db2614 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Mon, 9 Sep 2013 13:13:54 -0400 Subject: [PATCH 08/21] height WIP --- .../gallery/Geometry and Appearances.html | 147 ++++++++-------- Source/Core/PolylinePipeline.js | 10 +- Source/Core/PolylineVolumeGeometry.js | 5 +- Source/Core/PolylineVolumeGeometryLibrary.js | 161 ++++++++++++------ Source/Core/PolylineVolumeOutlineGeometry.js | 4 +- 5 files changed, 191 insertions(+), 136 deletions(-) diff --git a/Apps/Sandcastle/gallery/Geometry and Appearances.html b/Apps/Sandcastle/gallery/Geometry and Appearances.html index 7c87a40bbcb4..ccb35e416a42 100644 --- a/Apps/Sandcastle/gallery/Geometry and Appearances.html +++ b/Apps/Sandcastle/gallery/Geometry and Appearances.html @@ -44,7 +44,7 @@ geometry : new Cesium.ExtentGeometry({ extent : extent, vertexFormat : Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT, - stRotation: Cesium.Math.toRadians(45) + stRotation : Cesium.Math.toRadians(45) }) }); var extentOutlineInstance = new Cesium.GeometryInstance({ @@ -63,7 +63,7 @@ Cesium.Cartographic.fromDegrees(-97.0, 21.0), Cesium.Cartographic.fromDegrees(-97.0, 25.0) ]); - + var polygonInstance = new Cesium.GeometryInstance({ geometry : Cesium.PolygonGeometry.fromPositions({ positions : positions, @@ -89,7 +89,7 @@ semiMinorAxis : semiMinorAxis, semiMajorAxis : semiMajorAxis, rotation : rotation, - stRotation: Cesium.Math.toRadians(22), + stRotation : Cesium.Math.toRadians(22), vertexFormat : Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT }) }); @@ -111,7 +111,7 @@ geometry : new Cesium.CircleGeometry({ center : center, radius : radius, - stRotation: Cesium.Math.toRadians(90), + stRotation : Cesium.Math.toRadians(90), vertexFormat : Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT }) }); @@ -182,7 +182,7 @@ semiMajorAxis : semiMajorAxis, vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, height : height, - rotation: rotation, + rotation : rotation, extrudedHeight : extrudedHeight }), attributes : { @@ -195,7 +195,7 @@ semiMinorAxis : semiMinorAxis, semiMajorAxis : semiMajorAxis, height : height, - rotation: rotation, + rotation : rotation, extrudedHeight : extrudedHeight }), attributes : { @@ -219,7 +219,7 @@ polygonHierarchy : polygonHierarchy, vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, extrudedHeight : extrudedHeight, - height: height + height : height }), attributes : { color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 1.0})) @@ -229,7 +229,7 @@ geometry : new Cesium.PolygonOutlineGeometry({ polygonHierarchy : polygonHierarchy, extrudedHeight : extrudedHeight, - height: height + height : height }), attributes : { color : solidWhite @@ -407,25 +407,24 @@ Cesium.Cartographic.fromDegrees(-75.0, 50.0) ]); var maximumHeights = [500000, 1000000, 500000]; - var minimumHeights = [0, 500000, 0]; + var minimumHeights = [0, 500000, 0]; var wallInstance = new Cesium.GeometryInstance({ geometry : new Cesium.WallGeometry({ positions : positions, - maximumHeights: maximumHeights, - minimumHeights: minimumHeights - + maximumHeights : maximumHeights, + minimumHeights : minimumHeights }), attributes : { - color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha:0.7})) + color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 0.7})) } }); var wallOutlineInstance = new Cesium.GeometryInstance({ geometry : new Cesium.WallOutlineGeometry({ positions : positions, - maximumHeights: maximumHeights, - minimumHeights: minimumHeights + maximumHeights : maximumHeights, + minimumHeights : minimumHeights }), attributes : { color : new Cesium.ColorGeometryInstanceAttribute(0.7, 0.7, 0.7, 1.0) @@ -635,7 +634,7 @@ vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT }), attributes : { - color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha:0.5})) + color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 0.5})) } })); @@ -646,7 +645,7 @@ vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT }), attributes : { - color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha:0.5})) + color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 0.5})) } })); } @@ -777,7 +776,7 @@ faceForward : true }) })); - + positions = ellipsoid.cartographicArrayToCartesianArray([ Cesium.Cartographic.fromDegrees(-120.0, 45.0), Cesium.Cartographic.fromDegrees(-125.0, 50.0), @@ -786,7 +785,7 @@ var width = 100000; var corridor = new Cesium.GeometryInstance({ - geometry: new Cesium.CorridorGeometry({ + geometry : new Cesium.CorridorGeometry({ positions : positions, width : width, vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT @@ -795,43 +794,43 @@ color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 1.0})) } }); - + var extrudedCorridor = new Cesium.GeometryInstance({ - geometry: new Cesium.CorridorGeometry({ + geometry : new Cesium.CorridorGeometry({ positions : positions, width : width, vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, - height: 300000, - extrudedHeight: 400000 + height : 300000, + extrudedHeight : 400000 }), attributes : { color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 0.7})) } }); - + var corridorOutline = new Cesium.GeometryInstance({ - geometry: new Cesium.CorridorOutlineGeometry( { - positions: positions, - width: width, - height: 700000 + geometry : new Cesium.CorridorOutlineGeometry({ + positions : positions, + width : width, + height : 700000 }), - attributes: { + attributes : { color : solidWhite } }); - + var corridorFill = new Cesium.GeometryInstance({ - geometry: new Cesium.CorridorGeometry({ + geometry : new Cesium.CorridorGeometry({ positions : positions, width : width, vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, - height: 700000 + height : 700000 }), attributes : { color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 0.7})) } }); - + primitives.add(new Cesium.Primitive({ geometryInstances : [corridor, extrudedCorridor, corridorFill], appearance : new Cesium.PerInstanceColorAppearance({ @@ -840,7 +839,7 @@ faceForward : true }) })); - + primitives.add(new Cesium.Primitive({ geometryInstances : corridorOutline, appearance : new Cesium.PerInstanceColorAppearance({ @@ -857,56 +856,56 @@ function starPositions(arms, rOuter, rInner) { var angle = Math.PI / arms; var pos = []; - for (var i = 0; i < 2 * arms; i++) { + for ( var i = 0; i < 2 * arms; i++) { var r = (i % 2) === 0 ? rOuter : rInner; var p = new Cesium.Cartesian2(Math.cos(i * angle) * r, Math.sin(i * angle) * r); pos.push(p); } return pos; } - + positions = ellipsoid.cartographicArrayToCartesianArray([ - Cesium.Cartographic.fromDegrees(-90.0, 10.0), - Cesium.Cartographic.fromDegrees(-95.0, 15.0), - Cesium.Cartographic.fromDegrees(-100.0, 15.0) - ]); - - var tube = new Cesium.GeometryInstance({ - geometry: new Cesium.PolylineVolumeGeometry({ - polylinePositions : positions, - vertexFormat: Cesium.VertexFormat.ALL, - shapePositions: starPositions(7, 30000, 20000) - }), - attributes: { - color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 1.0})) - } - }); + Cesium.Cartographic.fromDegrees(-90.0, 10.0), + Cesium.Cartographic.fromDegrees(-95.0, 15.0), + Cesium.Cartographic.fromDegrees(-100.0, 15.0) + ]); + + var polylineVolume = new Cesium.GeometryInstance({ + geometry : new Cesium.PolylineVolumeGeometry({ + polylinePositions : positions, + vertexFormat : Cesium.VertexFormat.ALL, + shapePositions : starPositions(7, 30000, 20000) + }), + attributes : { + color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 1.0})) + } + }); height = 300000; - var tubeFill = new Cesium.GeometryInstance({ - geometry: new Cesium.PolylineVolumeGeometry({ - polylinePositions : positions, - vertexFormat: Cesium.VertexFormat.ALL, - shapePositions: starPositions(7, 30000, 20000), - height: height - }), - attributes: { - color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 1.0})) - } - }); + var polylineVolumeFill = new Cesium.GeometryInstance({ + geometry : new Cesium.PolylineVolumeGeometry({ + polylinePositions : positions, + vertexFormat : Cesium.VertexFormat.ALL, + shapePositions : starPositions(7, 30000, 20000), + height : height + }), + attributes : { + color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 1.0})) + } + }); - var tubeOutline = new Cesium.GeometryInstance({ - geometry: new Cesium.PolylineVolumeOutlineGeometry({ - polylinePositions : positions, - shapePositions: starPositions(7, 30000, 20000), - height: height - }), - attributes: { - color : solidWhite - } - }); + var polylineVolumeOutline = new Cesium.GeometryInstance({ + geometry : new Cesium.PolylineVolumeOutlineGeometry({ + polylinePositions : positions, + shapePositions : starPositions(7, 30000, 20000), + height : height + }), + attributes : { + color : solidWhite + } + }); primitives.add(new Cesium.Primitive({ - geometryInstances: [tube, tubeFill], + geometryInstances : [polylineVolume, polylineVolumeFill], appearance : new Cesium.PerInstanceColorAppearance({ translucent : false, closed : true @@ -914,7 +913,7 @@ })); primitives.add(new Cesium.Primitive({ - geometryInstances : tubeOutline, + geometryInstances : polylineVolumeOutline, appearance : new Cesium.PerInstanceColorAppearance({ flat : true, renderState : { diff --git a/Source/Core/PolylinePipeline.js b/Source/Core/PolylinePipeline.js index 9ef52528830d..5bf36d499986 100644 --- a/Source/Core/PolylinePipeline.js +++ b/Source/Core/PolylinePipeline.js @@ -46,16 +46,20 @@ define([ var carto1 = new Cartographic(); var carto2 = new Cartographic(); var cartesian = new Cartesian3(); + var scaleFirst = new Cartesian3(); + var scaleLast = new Cartesian3(); var ellipsoidGeodesic = new EllipsoidGeodesic(); //Returns subdivided line scaled to ellipsoid surface starting at p1 and ending at p2. //Result includes p1, but not include p2. This function is called for a sequence of line segments, //and this prevents duplication of end point. function generateCartesianArc(p1, p2, granularity, ellipsoid) { - var separationAngle = Cartesian3.angleBetween(p1, p2); + var first = ellipsoid.scaleToGeodeticSurface(p1, scaleFirst); + var last = ellipsoid.scaleToGeodeticSurface(p2, scaleLast); + var separationAngle = Cartesian3.angleBetween(first, last); var numPoints = Math.ceil(separationAngle/granularity); var result = new Array(numPoints*3); - var start = ellipsoid.cartesianToCartographic(p1, carto1); - var end = ellipsoid.cartesianToCartographic(p2, carto2); + var start = ellipsoid.cartesianToCartographic(first, carto1); + var end = ellipsoid.cartesianToCartographic(last, carto2); ellipsoidGeodesic.setEndPoints(start, end); var surfaceDistanceBetweenPoints = ellipsoidGeodesic.getSurfaceDistance() / (numPoints); diff --git a/Source/Core/PolylineVolumeGeometry.js b/Source/Core/PolylineVolumeGeometry.js index 1f85c498d87e..9e707a45f72b 100644 --- a/Source/Core/PolylineVolumeGeometry.js +++ b/Source/Core/PolylineVolumeGeometry.js @@ -45,8 +45,7 @@ define([ WindingOrder) { "use strict"; - - function computeAttributes(combinedPositions, shape, boundingRectangle, vertexFormat, ellipsoid) { + function computeAttributes(combinedPositions, shape, boundingRectangle, vertexFormat, ellipsoid) { var attributes = new GeometryAttributes(); if (vertexFormat.position) { attributes.position = new GeometryAttribute({ @@ -198,7 +197,7 @@ define([ * return positions; * } * - * var tube = new PolylineVolumeGeometry({ + * var volume = new PolylineVolumeGeometry({ * vertexFormat : VertexFormat.POSITION_ONLY, * polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ * Cartographic.fromDegrees(-72.0, 40.0), diff --git a/Source/Core/PolylineVolumeGeometryLibrary.js b/Source/Core/PolylineVolumeGeometryLibrary.js index 825d3c291ed3..b78ca6c77723 100644 --- a/Source/Core/PolylineVolumeGeometryLibrary.js +++ b/Source/Core/PolylineVolumeGeometryLibrary.js @@ -4,6 +4,7 @@ define([ './Cartesian2', './Cartesian3', './Cartesian4', + './Cartographic', './CornerType', './DeveloperError', './EllipsoidTangentPlane', @@ -18,6 +19,7 @@ define([ Cartesian2, Cartesian3, Cartesian4, + Cartographic, CornerType, DeveloperError, EllipsoidTangentPlane, @@ -45,38 +47,45 @@ define([ var PolylineVolumeGeometryLibrary = {}; - /** - * @private - */ - PolylineVolumeGeometryLibrary.removeDuplicates = function(shapePositions) { - var length = shapePositions.length; - var cleanedPositions = []; - for ( var i0 = length - 1, i1 = 0; i1 < length; i0 = i1++) { - var v0 = shapePositions[i0]; - var v1 = shapePositions[i1]; + var cartographic = new Cartographic(); + function scaleToSurface(positions, ellipsoid) { + var heights = new Array(positions.length); + for(var i = 0; i < positions.length; i++) { + var pos = positions[i]; + cartographic = ellipsoid.cartesianToCartographic(pos, cartographic); + heights[i] = cartographic.height; + positions[i] = ellipsoid.scaleToGeodeticSurface(pos); + } + return heights; + } - if (!Cartesian2.equals(v0, v1)) { - cleanedPositions.push(v1); // Shallow copy! + function subdivideHeights(points, h0, h1, granularity) { + var p0 = points[0]; + var p1 = points[1]; + var angleBetween = Cartesian3.angleBetween(p0, p1); + var numPoints = Math.ceil(angleBetween/granularity); + var heights = new Array(numPoints); + var i; + if (h0 === h1) { + for (i = 0; i < numPoints; i++) { + heights[i] = h0; } + heights.push(h1); + return heights; } - return cleanedPositions; - }; + var dHeight = h1 - h0; + var heightPerVertex = dHeight / (numPoints); - var originScratch = new Cartesian3(); - var nextScratch = new Cartesian3(); - var prevScratch = new Cartesian3(); - PolylineVolumeGeometryLibrary.angleIsGreaterThanPi = function(forward, backward, position, ellipsoid) { - var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid); - var origin = tangentPlane.projectPointOntoPlane(position, originScratch); - var next = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, forward, nextScratch), nextScratch); - var prev = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, backward, prevScratch), prevScratch); - - prev = Cartesian2.subtract(prev, origin, prev); - next = Cartesian2.subtract(next, origin, next); + for (i = 1; i < numPoints; i++) { + var h = h0 + i*heightPerVertex; + heights[i] = h; + } - return ((prev.x * next.y) - (prev.y * next.x)) >= 0.0; - }; + heights[0] = h0; + heights.push(h1); + return heights; + } function computeRotationAngle(start, end, position, ellipsoid) { var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid); @@ -88,13 +97,13 @@ define([ var angle = Cartesian2.angleBetween(next, prev); - return (((prev.x * next.y) - (prev.y * next.x)) >= 0.0) ? -angle : angle; + return (prev.x * next.y - prev.y * next.x >= 0.0) ? -angle : angle; } var negativeX = Cartesian4.UNIT_X.negate(); var transform = new Matrix4(); var translation = new Matrix4(); - var rotationMatrix = new Matrix3(); + var rotationZ = new Matrix3(); var scaleMatrix = Matrix3.IDENTITY.clone(); var westScratch = new Cartesian3(); var finalPosScratch = new Cartesian3(); @@ -102,15 +111,16 @@ define([ function addPosition(center, left, shape, finalPositions, ellipsoid, height, xScalar, repeat) { var west = westScratch; var finalPosition = finalPosScratch; - transform = Transforms.eastNorthUpToFixedFrame(center, ellipsoid, transform); + west = Matrix4.multiplyByVector(transform, negativeX, west); west = Cartesian3.fromCartesian4(west, west); west = Cartesian3.normalize(west, west); var angle = computeRotationAngle(west, left, center, ellipsoid); - var rotation = Matrix3.fromRotationZ(angle, rotationMatrix); + rotationZ = Matrix3.fromRotationZ(angle, rotationZ); + heightCartesian.z = height; - transform = Matrix4.multiply(transform, Matrix4.fromRotationTranslation(rotation, heightCartesian, translation), transform); + transform = Matrix4.multiply(transform, Matrix4.fromRotationTranslation(rotationZ, heightCartesian, translation), transform); var scale = scaleMatrix; scale[0] = xScalar; @@ -127,10 +137,10 @@ define([ } var centerScratch = new Cartesian3(); - function addPositions(centers, left, shape, finalPositions, ellipsoid, height, xScalar) { + function addPositions(centers, left, shape, finalPositions, ellipsoid, heights, xScalar) { for ( var i = 0; i < centers.length; i += 3) { var center = Cartesian3.fromArray(centers, i, centerScratch); - finalPositions = addPosition(center, left, shape, finalPositions, ellipsoid, height, xScalar, 1); + finalPositions = addPosition(center, left, shape, finalPositions, ellipsoid, heights[i/3], xScalar, 1); } return finalPositions; } @@ -197,6 +207,8 @@ define([ } var left; + var surfacePoint; + startPoint = Cartesian3.clone(startPoint, startPointScratch); if (granularity > 0) { var repeat = duplicatePoints ? 2 : 1; for ( var i = 0; i < granularity; i++) { @@ -206,17 +218,17 @@ define([ if (!leftIsOutside) { left = Cartesian3.negate(left, left); } - var surfacePoint = ellipsoid.scaleToGeodeticSurface(startPoint, scratch2); + surfacePoint = ellipsoid.scaleToGeodeticSurface(startPoint, scratch2); finalPositions = addPosition(surfacePoint, left, shape, finalPositions, ellipsoid, height, 1, repeat); } } else { - startPoint = Cartesian3.clone(startPoint, startPointScratch); left = Cartesian3.subtract(startPoint, pivot, scratch1); left = Cartesian3.normalize(left, left); if (!leftIsOutside) { left = Cartesian3.negate(left, left); } - finalPositions = addPosition(startPoint, left, shape, finalPositions, ellipsoid, height, 1, 1); + surfacePoint = ellipsoid.scaleToGeodeticSurface(startPoint, scratch2); + finalPositions = addPosition(surfacePoint, left, shape, finalPositions, ellipsoid, height, 1, 1); endPoint = Cartesian3.clone(endPoint, startPointScratch); left = Cartesian3.subtract(endPoint, pivot, scratch1); @@ -224,22 +236,57 @@ define([ if (!leftIsOutside) { left = Cartesian3.negate(left, left); } - finalPositions = addPosition(endPoint, left, shape, finalPositions, ellipsoid, height, 1, 1); + surfacePoint = ellipsoid.scaleToGeodeticSurface(endPoint, scratch2); + finalPositions = addPosition(surfacePoint, left, shape, finalPositions, ellipsoid, height, 1, 1); } return finalPositions; } + /** + * @private + */ + PolylineVolumeGeometryLibrary.removeDuplicates = function(shapePositions) { + var length = shapePositions.length; + var cleanedPositions = []; + for ( var i0 = length - 1, i1 = 0; i1 < length; i0 = i1++) { + var v0 = shapePositions[i0]; + var v1 = shapePositions[i1]; + + if (!Cartesian2.equals(v0, v1)) { + cleanedPositions.push(v1); // Shallow copy! + } + } + + return cleanedPositions; + }; + + var originScratch = new Cartesian3(); + var nextScratch = new Cartesian3(); + var prevScratch = new Cartesian3(); + PolylineVolumeGeometryLibrary.angleIsGreaterThanPi = function(forward, backward, position, ellipsoid) { + var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid); + var origin = tangentPlane.projectPointOntoPlane(position, originScratch); + var next = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, forward, nextScratch), nextScratch); + var prev = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, backward, prevScratch), prevScratch); + + prev = Cartesian2.subtract(prev, origin, prev); + next = Cartesian2.subtract(next, origin, next); + + return ((prev.x * next.y) - (prev.y * next.x)) >= 0.0; + }; + /** * @private */ PolylineVolumeGeometryLibrary.computePositions = function(positions, shape2D, boundingRectangle, geometry, duplicatePoints) { + var ellipsoid = geometry._ellipsoid; + var heights = scaleToSurface(positions, ellipsoid); var granularity = geometry._granularity; var cornerType = geometry._cornerType; - var ellipsoid = geometry._ellipsoid; var shapeForSides = duplicatePoints ? convertShapeTo3DDuplicate(shape2D, boundingRectangle) : convertShapeTo3D(shape2D, boundingRectangle); var shapeForEnds = duplicatePoints ? convertShapeTo3D(shape2D, boundingRectangle) : undefined; - var height = geometry._height + boundingRectangle.height / 2; + var heightOffset = boundingRectangle.height / 2; var width = boundingRectangle.width / 2; var length = positions.length; var finalPositions = []; @@ -262,14 +309,16 @@ define([ forward = Cartesian3.normalize(forward, forward); left = Cartesian3.cross(surfaceNormal, forward, left); left = Cartesian3.normalize(left, left); + var h0 = heights[0]; + var h1 = heights[1]; if (duplicatePoints) { - ends = addPosition(position, left, shapeForEnds, ends, ellipsoid, height, 1, 1); + ends = addPosition(position, left, shapeForEnds, ends, ellipsoid, h0 + heightOffset, 1, 1); } previousPosition = Cartesian3.clone(position, previousPosition); position = nextPosition; backward = Cartesian3.negate(forward, backward); - - var surfacePositions; + var subdividedHeights; + var subdividedPositions; for ( var i = 1; i < length - 1; i++) { var repeat = duplicatePoints ? 2 : 1; nextPosition = positions[i + 1]; @@ -291,16 +340,17 @@ define([ start = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, width, start), start); scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]); scratch2Array[1] = Cartesian3.clone(start, scratch2Array[1]); - surfacePositions = PolylinePipeline.scaleToSurface(scratch2Array); - finalPositions = addPositions(surfacePositions, left, shapeForSides, finalPositions, ellipsoid, height, 1); + subdividedHeights = subdivideHeights(scratch2Array, h0 + heightOffset, h1 + heightOffset, granularity); + subdividedPositions = PolylinePipeline.scaleToSurface(scratch2Array); + finalPositions = addPositions(subdividedPositions, left, shapeForSides, finalPositions, ellipsoid, subdividedHeights, 1); left = Cartesian3.cross(surfaceNormal, forward, left); left = Cartesian3.normalize(left, left); end = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, width, end), end); if (cornerType.value === CornerType.ROUNDED.value || cornerType.value === CornerType.BEVELED.value) { - computeRoundCorner(pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, height, duplicatePoints); + computeRoundCorner(pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, h1 + heightOffset, duplicatePoints); } else { cornerDirection = Cartesian3.negate(cornerDirection, cornerDirection); - finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, height, ratio, repeat); + finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, h1 + heightOffset, ratio, repeat); } previousPosition = Cartesian3.clone(end, previousPosition); } else { @@ -308,28 +358,33 @@ define([ start = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, -width, start), start); scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]); scratch2Array[1] = Cartesian3.clone(start, scratch2Array[1]); - surfacePositions = PolylinePipeline.scaleToSurface(scratch2Array, granularity, ellipsoid); - finalPositions = addPositions(surfacePositions, left, shapeForSides, finalPositions, ellipsoid, height, 1); + subdividedHeights = subdivideHeights(scratch2Array, h0 + heightOffset, h1 + heightOffset, granularity); + subdividedPositions = PolylinePipeline.scaleToSurface(scratch2Array, granularity, ellipsoid); + finalPositions = addPositions(subdividedPositions, left, shapeForSides, finalPositions, ellipsoid, subdividedHeights, 1); left = Cartesian3.cross(surfaceNormal, forward, left); left = Cartesian3.normalize(left, left); end = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, -width, end), end); if (cornerType.value === CornerType.ROUNDED.value || cornerType.value === CornerType.BEVELED.value) { - computeRoundCorner(pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, height, duplicatePoints); + computeRoundCorner(pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, h1 + heightOffset, duplicatePoints); } else { - finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, height, ratio, repeat); + finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, h1 + heightOffset, ratio, repeat); } previousPosition = Cartesian3.clone(end, previousPosition); } backward = Cartesian3.negate(forward, backward); } + h0 = h1; + h1 = heights[i + 1]; position = nextPosition; } + scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]); scratch2Array[1] = Cartesian3.clone(position, scratch2Array[1]); - surfacePositions = PolylinePipeline.scaleToSurface(scratch2Array, granularity, ellipsoid); - finalPositions = addPositions(surfacePositions, left, shapeForSides, finalPositions, ellipsoid, height, 1); + subdividedHeights = subdivideHeights(scratch2Array, h0 + heightOffset, h1 + heightOffset, granularity); + subdividedPositions = PolylinePipeline.scaleToSurface(scratch2Array, granularity, ellipsoid); + finalPositions = addPositions(subdividedPositions, left, shapeForSides, finalPositions, ellipsoid, subdividedHeights, 1); if (duplicatePoints) { - ends = addPosition(position, left, shapeForEnds, ends, ellipsoid, height, 1, 1); + ends = addPosition(position, left, shapeForEnds, ends, ellipsoid, h1 + heightOffset, 1, 1); } length = finalPositions.length; diff --git a/Source/Core/PolylineVolumeOutlineGeometry.js b/Source/Core/PolylineVolumeOutlineGeometry.js index 2d1f303bc990..e2ae3c469a23 100644 --- a/Source/Core/PolylineVolumeOutlineGeometry.js +++ b/Source/Core/PolylineVolumeOutlineGeometry.js @@ -105,7 +105,6 @@ define([ * @param {Number} options.shapePositions An array of {Cartesian2} positions that define the shape to be extruded along the polyline * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference. * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer. - * @param {Number} [options.height=0] The distance between the ellipsoid surface and the positions. * @param {Boolean} [options.cornerType = CornerType.ROUNDED] Determines the style of the corners. * * @exception {DeveloperError} options.positions is required. @@ -124,7 +123,7 @@ define([ * return positions; * } * - * var tubeOutline = new PolylineVolumeOutlineGeometry({ + * var volumeOutline = new PolylineVolumeOutlineGeometry({ * polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ * Cartographic.fromDegrees(-72.0, 40.0), * Cartographic.fromDegrees(-70.0, 35.0) @@ -146,7 +145,6 @@ define([ this._positions = positions; this._shape = shape; this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); - this._height = defaultValue(options.height, 0); this._cornerType = defaultValue(options.cornerType, CornerType.ROUNDED); this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); this._workerName = 'createPolylineVolumeOutlineGeometry'; From 4bbe01efcd2544390309543683e8e00d53033f7d Mon Sep 17 00:00:00 2001 From: hpinkos Date: Mon, 16 Sep 2013 15:27:24 -0400 Subject: [PATCH 09/21] fix mitered corners --- Source/Core/CorridorGeometryLibrary.js | 10 +++++++++- Source/Core/PolylineVolumeGeometryLibrary.js | 13 ++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Source/Core/CorridorGeometryLibrary.js b/Source/Core/CorridorGeometryLibrary.js index dd069eabb93e..141d484097cf 100644 --- a/Source/Core/CorridorGeometryLibrary.js +++ b/Source/Core/CorridorGeometryLibrary.js @@ -155,14 +155,22 @@ define([ } }; + function scaleToSurface(positions, ellipsoid) { + for(var i = 0; i < positions.length; i++) { + positions[i] = ellipsoid.scaleToGeodeticSurface(positions[i], positions[i]); + } + return positions; + } + /** * @private */ CorridorGeometryLibrary.computePositions = function (params) { var granularity = params.granularity; var positions = params.positions; - var width = params.width / 2; var ellipsoid = params.ellipsoid; + positions = scaleToSurface(positions, ellipsoid); + var width = params.width / 2; var cornerType = params.cornerType; var saveAttributes = params.saveAttributes; var normal = cartesian1; diff --git a/Source/Core/PolylineVolumeGeometryLibrary.js b/Source/Core/PolylineVolumeGeometryLibrary.js index b78ca6c77723..fe803a00c725 100644 --- a/Source/Core/PolylineVolumeGeometryLibrary.js +++ b/Source/Core/PolylineVolumeGeometryLibrary.js @@ -331,12 +331,11 @@ define([ if (doCorner) { cornerDirection = Cartesian3.cross(cornerDirection, surfaceNormal, cornerDirection); cornerDirection = Cartesian3.cross(surfaceNormal, cornerDirection, cornerDirection); - var scalar = width / Math.max(0.25, (Cartesian3.magnitude(Cartesian3.cross(cornerDirection, backward, scratch1)))); + cornerDirection = Cartesian3.normalize(cornerDirection, cornerDirection); + var scalar = 1 / Math.max(0.25, (Cartesian3.magnitude(Cartesian3.cross(cornerDirection, backward, scratch1)))); var leftIsOutside = PolylineVolumeGeometryLibrary.angleIsGreaterThanPi(forward, backward, position, ellipsoid); - cornerDirection = Cartesian3.multiplyByScalar(cornerDirection, scalar, cornerDirection); - var ratio = scalar / width; if (leftIsOutside) { - pivot = Cartesian3.add(position, cornerDirection, pivot); + pivot = Cartesian3.add(position, Cartesian3.multiplyByScalar(cornerDirection, scalar*width, cornerDirection), pivot); start = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, width, start), start); scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]); scratch2Array[1] = Cartesian3.clone(start, scratch2Array[1]); @@ -350,11 +349,11 @@ define([ computeRoundCorner(pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, h1 + heightOffset, duplicatePoints); } else { cornerDirection = Cartesian3.negate(cornerDirection, cornerDirection); - finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, h1 + heightOffset, ratio, repeat); + finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, h1 + heightOffset, scalar, repeat); } previousPosition = Cartesian3.clone(end, previousPosition); } else { - pivot = Cartesian3.add(position, cornerDirection, pivot); + pivot = Cartesian3.add(position, Cartesian3.multiplyByScalar(cornerDirection, scalar*width, cornerDirection), pivot); start = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, -width, start), start); scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]); scratch2Array[1] = Cartesian3.clone(start, scratch2Array[1]); @@ -367,7 +366,7 @@ define([ if (cornerType.value === CornerType.ROUNDED.value || cornerType.value === CornerType.BEVELED.value) { computeRoundCorner(pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, h1 + heightOffset, duplicatePoints); } else { - finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, h1 + heightOffset, ratio, repeat); + finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, h1 + heightOffset, scalar, repeat); } previousPosition = Cartesian3.clone(end, previousPosition); } From 03d9b116f482e01719a2fd8f015e57ca4e345c18 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Mon, 16 Sep 2013 15:36:50 -0400 Subject: [PATCH 10/21] cleanup --- Source/Core/PolylineVolumeGeometry.js | 4 ++-- Source/Core/PolylineVolumeGeometryLibrary.js | 8 ++++---- Source/Core/PolylineVolumeOutlineGeometry.js | 4 ++-- Specs/Core/PolylineVolumeGeometrySpec.js | 2 -- Specs/Core/PolylineVolumeOutlineGeometrySpec.js | 1 - 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Source/Core/PolylineVolumeGeometry.js b/Source/Core/PolylineVolumeGeometry.js index 9e707a45f72b..1acf13fc9194 100644 --- a/Source/Core/PolylineVolumeGeometry.js +++ b/Source/Core/PolylineVolumeGeometry.js @@ -189,7 +189,7 @@ define([ * @example * function computeCirclePositions(radius) { * var positions = []; - * var theta = CesiumMath.toRadians(1); + * var theta = CesiumMath.toRadians(1.0); * var posCount = Math.PI*2/theta; * for (var i = 0; i < posCount; i++) { * positions.push(new Cartesian2(radius * Math.cos(theta * i), radius * Math.sin(theta * i))); @@ -203,7 +203,7 @@ define([ * Cartographic.fromDegrees(-72.0, 40.0), * Cartographic.fromDegrees(-70.0, 35.0) * ]), - * shapePositions : circlePositions(10000) + * shapePositions : circlePositions(10000.0) * }); */ var PolylineVolumeGeometry = function(options) { diff --git a/Source/Core/PolylineVolumeGeometryLibrary.js b/Source/Core/PolylineVolumeGeometryLibrary.js index fe803a00c725..e60a63056e3d 100644 --- a/Source/Core/PolylineVolumeGeometryLibrary.js +++ b/Source/Core/PolylineVolumeGeometryLibrary.js @@ -154,23 +154,23 @@ define([ var point = shape2D[0]; shape[index++] = point.x - xOffset; - shape[index++] = 0; + shape[index++] = 0.0; shape[index++] = point.y - yOffset; for ( var i = 1; i < length; i++) { point = shape2D[i]; var x = point.x - xOffset; var z = point.y - yOffset; shape[index++] = x; - shape[index++] = 0; + shape[index++] = 0.0; shape[index++] = z; shape[index++] = x; - shape[index++] = 0; + shape[index++] = 0.0; shape[index++] = z; } point = shape2D[0]; shape[index++] = point.x - xOffset; - shape[index++] = 0; + shape[index++] = 0.0; shape[index++] = point.y - yOffset; return shape; diff --git a/Source/Core/PolylineVolumeOutlineGeometry.js b/Source/Core/PolylineVolumeOutlineGeometry.js index e2ae3c469a23..a70e373207de 100644 --- a/Source/Core/PolylineVolumeOutlineGeometry.js +++ b/Source/Core/PolylineVolumeOutlineGeometry.js @@ -115,7 +115,7 @@ define([ * @example * function computeCirclePositions(radius) { * var positions = []; - * var theta = CesiumMath.toRadians(1); + * var theta = CesiumMath.toRadians(1.0); * var posCount = Math.PI*2/theta; * for (var i = 0; i < posCount; i++) { * positions.push(new Cartesian2(radius * Math.cos(theta * i), radius * Math.sin(theta * i))); @@ -128,7 +128,7 @@ define([ * Cartographic.fromDegrees(-72.0, 40.0), * Cartographic.fromDegrees(-70.0, 35.0) * ]), - * shapePositions : circlePositions(10000) + * shapePositions : circlePositions(10000.0) * }); */ var PolylineVolumeOutlineGeometry = function(options) { diff --git a/Specs/Core/PolylineVolumeGeometrySpec.js b/Specs/Core/PolylineVolumeGeometrySpec.js index 9f9a0b1103fc..1965b82efb07 100644 --- a/Specs/Core/PolylineVolumeGeometrySpec.js +++ b/Specs/Core/PolylineVolumeGeometrySpec.js @@ -25,7 +25,6 @@ defineSuite([ shape = [new Cartesian2(-10000, -10000), new Cartesian2(10000, -10000), new Cartesian2(10000, 10000), new Cartesian2(-10000, 10000)]; }); - it('throws without polyline positions', function() { expect(function() { return new PolylineVolumeGeometry({}); @@ -90,7 +89,6 @@ defineSuite([ expect(m.indices.length).toEqual(3 * 10 * 2 + 24 * 3); }); - it('compute all vertex attributes', function() { var ellipsoid = Ellipsoid.WGS84; var m = PolylineVolumeGeometry.createGeometry(new PolylineVolumeGeometry({ diff --git a/Specs/Core/PolylineVolumeOutlineGeometrySpec.js b/Specs/Core/PolylineVolumeOutlineGeometrySpec.js index c21c2a7f912e..550d77acd4e3 100644 --- a/Specs/Core/PolylineVolumeOutlineGeometrySpec.js +++ b/Specs/Core/PolylineVolumeOutlineGeometrySpec.js @@ -24,7 +24,6 @@ defineSuite([ shape = [new Cartesian2(-10000, -10000), new Cartesian2(10000, -10000), new Cartesian2(10000, 10000), new Cartesian2(-10000, 10000)]; }); - it('throws without polyline positions', function() { expect(function() { return new PolylineVolumeOutlineGeometry({}); From d0e34f736fd42cb7e861a557b2f16abfad87c418 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Tue, 17 Sep 2013 12:03:36 -0400 Subject: [PATCH 11/21] add circle to shapes --- .../gallery/Geometry and Appearances.html | 54 ++++++++++++------- Source/Core/GeometryAttributes.js | 2 +- Source/Core/PolylineVolumeGeometry.js | 12 +---- Source/Core/PolylineVolumeOutlineGeometry.js | 12 +---- Source/Core/Shapes.js | 35 ++++++++++-- Specs/Core/ShapesSpec.js | 14 +++++ 6 files changed, 82 insertions(+), 47 deletions(-) diff --git a/Apps/Sandcastle/gallery/Geometry and Appearances.html b/Apps/Sandcastle/gallery/Geometry and Appearances.html index d6b61fc56da9..9fd69430a006 100644 --- a/Apps/Sandcastle/gallery/Geometry and Appearances.html +++ b/Apps/Sandcastle/gallery/Geometry and Appearances.html @@ -919,39 +919,55 @@ } positions = ellipsoid.cartographicArrayToCartesianArray([ - Cesium.Cartographic.fromDegrees(-90.0, 10.0), - Cesium.Cartographic.fromDegrees(-95.0, 15.0), - Cesium.Cartographic.fromDegrees(-100.0, 15.0) + Cesium.Cartographic.fromDegrees(-102.0, 15.0, 100000.0), + Cesium.Cartographic.fromDegrees(-105.0, 20.0, 200000.0), + Cesium.Cartographic.fromDegrees(-110.0, 20.0, 100000.0) ]); - - var polylineVolume = new Cesium.GeometryInstance({ + var polylineVolumeFill = new Cesium.GeometryInstance({ geometry : new Cesium.PolylineVolumeGeometry({ polylinePositions : positions, - vertexFormat : Cesium.VertexFormat.ALL, - shapePositions : starPositions(7, 30000, 20000) + vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, + shapePositions : starPositions(7, 30000.0, 20000.0) }), attributes : { color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 1.0})) } }); - height = 300000; - var polylineVolumeFill = new Cesium.GeometryInstance({ - geometry : new Cesium.PolylineVolumeGeometry({ + + var polylineVolumeOutline = new Cesium.GeometryInstance({ + geometry : new Cesium.PolylineVolumeOutlineGeometry({ polylinePositions : positions, - vertexFormat : Cesium.VertexFormat.ALL, - shapePositions : starPositions(7, 30000, 20000), - height : height + shapePositions : starPositions(7, 30000.0, 20000.0) + }), + attributes : { + color : solidWhite + } + }); + + var polylineVolume = new Cesium.GeometryInstance({ + geometry : new Cesium.PolylineVolumeGeometry({ + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cesium.Cartographic.fromDegrees(-102.0, 15.0), + Cesium.Cartographic.fromDegrees(-105.0, 20.0), + Cesium.Cartographic.fromDegrees(-110.0, 20.0) + ]), + vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, + shapePositions : starPositions(7, 30000, 20000) }), attributes : { color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 1.0})) } }); - var polylineVolumeOutline = new Cesium.GeometryInstance({ - geometry : new Cesium.PolylineVolumeOutlineGeometry({ - polylinePositions : positions, - shapePositions : starPositions(7, 30000, 20000), - height : height + var tubeGeometry = new Cesium.GeometryInstance({ + geometry : new Cesium.PolylineVolumeGeometry({ + polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ + Cesium.Cartographic.fromDegrees(-104.0, 13.0), + Cesium.Cartographic.fromDegrees(-107.0, 18.0), + Cesium.Cartographic.fromDegrees(-112.0, 18.0) + ]), + vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, + shapePositions : Cesium.Shapes.compute2DCircle(40000.0) }), attributes : { color : solidWhite @@ -959,7 +975,7 @@ }); primitives.add(new Cesium.Primitive({ - geometryInstances : [polylineVolume, polylineVolumeFill], + geometryInstances : [tubeGeometry, polylineVolume, polylineVolumeFill], appearance : new Cesium.PerInstanceColorAppearance({ translucent : false, closed : true diff --git a/Source/Core/GeometryAttributes.js b/Source/Core/GeometryAttributes.js index b9e740ce5b46..75563221096a 100644 --- a/Source/Core/GeometryAttributes.js +++ b/Source/Core/GeometryAttributes.js @@ -11,7 +11,7 @@ define(['./defaultValue'], function(defaultValue) { * into the vertex buffer for better rendering performance. *

* - * @alias VertexFormat + * @alias GeometryAttributes * @constructor */ var GeometryAttributes = function(options) { diff --git a/Source/Core/PolylineVolumeGeometry.js b/Source/Core/PolylineVolumeGeometry.js index 1acf13fc9194..237e036d51d9 100644 --- a/Source/Core/PolylineVolumeGeometry.js +++ b/Source/Core/PolylineVolumeGeometry.js @@ -187,23 +187,13 @@ define([ * @see PolylineVolumeGeometry#createGeometry * * @example - * function computeCirclePositions(radius) { - * var positions = []; - * var theta = CesiumMath.toRadians(1.0); - * var posCount = Math.PI*2/theta; - * for (var i = 0; i < posCount; i++) { - * positions.push(new Cartesian2(radius * Math.cos(theta * i), radius * Math.sin(theta * i))); - * } - * return positions; - * } - * * var volume = new PolylineVolumeGeometry({ * vertexFormat : VertexFormat.POSITION_ONLY, * polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ * Cartographic.fromDegrees(-72.0, 40.0), * Cartographic.fromDegrees(-70.0, 35.0) * ]), - * shapePositions : circlePositions(10000.0) + * shapePositions : Shapes.compute2DCircle(100000.0) * }); */ var PolylineVolumeGeometry = function(options) { diff --git a/Source/Core/PolylineVolumeOutlineGeometry.js b/Source/Core/PolylineVolumeOutlineGeometry.js index a70e373207de..cee6ffdee115 100644 --- a/Source/Core/PolylineVolumeOutlineGeometry.js +++ b/Source/Core/PolylineVolumeOutlineGeometry.js @@ -113,22 +113,12 @@ define([ * @see PolylineVolumeOutlineGeometry#createGeometry * * @example - * function computeCirclePositions(radius) { - * var positions = []; - * var theta = CesiumMath.toRadians(1.0); - * var posCount = Math.PI*2/theta; - * for (var i = 0; i < posCount; i++) { - * positions.push(new Cartesian2(radius * Math.cos(theta * i), radius * Math.sin(theta * i))); - * } - * return positions; - * } - * * var volumeOutline = new PolylineVolumeOutlineGeometry({ * polylinePositions : ellipsoid.cartographicArrayToCartesianArray([ * Cartographic.fromDegrees(-72.0, 40.0), * Cartographic.fromDegrees(-70.0, 35.0) * ]), - * shapePositions : circlePositions(10000.0) + * shapePositions : Shapes.compute2DCircle(100000.0) * }); */ var PolylineVolumeOutlineGeometry = function(options) { diff --git a/Source/Core/Shapes.js b/Source/Core/Shapes.js index 4c7c2f2bf43e..52891f678dd3 100644 --- a/Source/Core/Shapes.js +++ b/Source/Core/Shapes.js @@ -4,6 +4,7 @@ define([ './defined', './DeveloperError', './Math', + './Cartesian2', './Cartesian3', './Quaternion', './Matrix3' @@ -12,12 +13,13 @@ define([ defined, DeveloperError, CesiumMath, + Cartesian2, Cartesian3, Quaternion, Matrix3) { "use strict"; - function _computeEllipseQuadrant(cb, cbRadius, aSqr, bSqr, ab, ecc, mag, unitPos, eastVec, northVec, bearing, + function computeEllipseQuadrant(cb, cbRadius, aSqr, bSqr, ab, ecc, mag, unitPos, eastVec, northVec, bearing, thetaPts, thetaPtsIndex, offset, clockDir, ellipsePts, ellipsePtsIndex, numPts) { var angle; var theta; @@ -212,21 +214,44 @@ define([ var ellipsePts = []; - _computeEllipseQuadrant(ellipsoid, surfPos.magnitude(), aSqr, bSqr, ab, ecc, mag, unitPos, eastVec, northVec, bearing, + computeEllipseQuadrant(ellipsoid, surfPos.magnitude(), aSqr, bSqr, ab, ecc, mag, unitPos, eastVec, northVec, bearing, thetaPts, 0.0, 0.0, 1, ellipsePts, 0, numQuadrantPts - 1); - _computeEllipseQuadrant(ellipsoid, surfPos.magnitude(), aSqr, bSqr, ab, ecc, mag, unitPos, eastVec, northVec, bearing, + computeEllipseQuadrant(ellipsoid, surfPos.magnitude(), aSqr, bSqr, ab, ecc, mag, unitPos, eastVec, northVec, bearing, thetaPts, numQuadrantPts - 1, Math.PI, -1, ellipsePts, numQuadrantPts - 1, numQuadrantPts - 1); - _computeEllipseQuadrant(ellipsoid, surfPos.magnitude(), aSqr, bSqr, ab, ecc, mag, unitPos, eastVec, northVec, bearing, + computeEllipseQuadrant(ellipsoid, surfPos.magnitude(), aSqr, bSqr, ab, ecc, mag, unitPos, eastVec, northVec, bearing, thetaPts, 0.0, Math.PI, 1, ellipsePts, (2 * numQuadrantPts) - 2, numQuadrantPts - 1); - _computeEllipseQuadrant(ellipsoid, surfPos.magnitude(), aSqr, bSqr, ab, ecc, mag, unitPos, eastVec, northVec, bearing, + computeEllipseQuadrant(ellipsoid, surfPos.magnitude(), aSqr, bSqr, ab, ecc, mag, unitPos, eastVec, northVec, bearing, thetaPts, numQuadrantPts - 1, CesiumMath.TWO_PI, -1, ellipsePts, (3 * numQuadrantPts) - 3, numQuadrantPts); ellipsePts.push(ellipsePts[0].clone()); // Duplicates first and last point for polyline return ellipsePts; + }, + + /** + * Computes a 2D circle about the origin. + * + * @param {Number} [radius = 1.0] The radius of the circle + * + * @returns The set of points that form the ellipse's boundary. + * + * @example + * var circle = Shapes.compute2DCircle(100000.0); + */ + compute2DCircle : function(radius) { + if (!defined(radius)) { + radius = 1.0; + } + var positions = []; + var theta = CesiumMath.toRadians(1.0); + var posCount = Math.PI*2/theta; + for (var i = 0; i < posCount; i++) { + positions.push(new Cartesian2(radius * Math.cos(theta * i), radius * Math.sin(theta * i))); + } + return positions; } }; diff --git a/Specs/Core/ShapesSpec.js b/Specs/Core/ShapesSpec.js index 39fe89161dfe..9f14705da045 100644 --- a/Specs/Core/ShapesSpec.js +++ b/Specs/Core/ShapesSpec.js @@ -119,4 +119,18 @@ defineSuite([ Shapes.computeEllipseBoundary(ellipsoid, center, 1.0, 1.0, 0, -1.0); }).toThrow(); }); + + it('compute2DCircle returns a unit circle by default', function() { + var points = Shapes.compute2DCircle(); + expect(points.length).toBeGreaterThan(0); + expect(points[0].x).toEqual(1); + expect(points[0].y).toEqual(0); + }); + + it('compute2DCircle returns an circle with radius 5', function() { + var points = Shapes.compute2DCircle(5.0); + expect(points.length).toBeGreaterThan(0); + expect(points[0].x).toEqual(5); + expect(points[0].y).toEqual(0); + }); }); From cd94709132c1dcab7a9034d6547c27f92c613f3c Mon Sep 17 00:00:00 2001 From: hpinkos Date: Mon, 23 Sep 2013 15:18:14 -0400 Subject: [PATCH 12/21] fix merge --- Source/Core/PolylineVolumeGeometryLibrary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Core/PolylineVolumeGeometryLibrary.js b/Source/Core/PolylineVolumeGeometryLibrary.js index e60a63056e3d..7711b78bc07f 100644 --- a/Source/Core/PolylineVolumeGeometryLibrary.js +++ b/Source/Core/PolylineVolumeGeometryLibrary.js @@ -100,7 +100,7 @@ define([ return (prev.x * next.y - prev.y * next.x >= 0.0) ? -angle : angle; } - var negativeX = Cartesian4.UNIT_X.negate(); + var negativeX = new Cartesian4(-1, 0, 0, 0); var transform = new Matrix4(); var translation = new Matrix4(); var rotationZ = new Matrix3(); From d8f6c6c030991e0405a0a00271fddae2ac02588d Mon Sep 17 00:00:00 2001 From: hpinkos Date: Wed, 25 Sep 2013 09:18:32 -0400 Subject: [PATCH 13/21] fix changes.md --- CHANGES.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 6bfd88cbd88c..db90853ee079 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -57,9 +57,12 @@ Beta Releases * Fix `EllipsoidTangentPlane.projectPointOntoPlane` for tangent planes on an ellipsoid other than the unit sphere. * Added prototype `clone` and `merge` functions to `DynamicScene` graphics objects. * Added `width`, `height`, and `nearFarScalar` properties to `DynamicBillboard` for controlling the image size. -* Added `getDrawingBufferWidth` and `getDrawingBufferHeight` to `Context`.* Added `PolylineVolumeGeometry` and `PolylineVolumeGeometryOutline`.* `Viewer` no longer losses visual fidelity when using browser zoom. +* Added `getDrawingBufferWidth` and `getDrawingBufferHeight` to `Context`. +* `Viewer` no longer losses visual fidelity when using browser zoom. * Added `Geometries` tab to Sandcastle with an example for each geometry type. +* Added `PolylineVolumeGeometry` and `PolylineVolumeGeometryOutline`. * Added `Shapes.compute2DCircle`. + ### b20 - 2013-09-03 _This releases fixes 2D and other issues with Chrome 29.0.1547.57 ([#1002](https://github.com/AnalyticalGraphicsInc/cesium/issues/1002) and [#1047](https://github.com/AnalyticalGraphicsInc/cesium/issues/1047))._ From 50d8dcfe4897b5476ae68951fb2a08793d30d51a Mon Sep 17 00:00:00 2001 From: hpinkos Date: Fri, 27 Sep 2013 12:03:34 -0400 Subject: [PATCH 14/21] cleanup --- .../gallery/Geometry and Appearances.html | 35 +++++------ Source/Core/PolylineVolumeGeometry.js | 63 +++++++++++-------- Source/Core/PolylineVolumeGeometryLibrary.js | 23 +++---- Specs/Core/PolylineVolumeGeometrySpec.js | 4 +- 4 files changed, 64 insertions(+), 61 deletions(-) diff --git a/Apps/Sandcastle/gallery/Geometry and Appearances.html b/Apps/Sandcastle/gallery/Geometry and Appearances.html index 1b4fb6722f64..4ff9c06c0030 100644 --- a/Apps/Sandcastle/gallery/Geometry and Appearances.html +++ b/Apps/Sandcastle/gallery/Geometry and Appearances.html @@ -28,13 +28,10 @@ "use strict"; Cesium.Math.setRandomNumberSeed(1234); - var viewer = new Cesium.Viewer('cesiumContainer'); - var scene = viewer.scene; var primitives = scene.getPrimitives(); var ellipsoid = viewer.centralBody.getEllipsoid(); - var solidWhite = Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.WHITE); // Combine instances for an extent, polygon, ellipse, and circle into a single primitive. @@ -241,7 +238,8 @@ var topRadius = 150000.0; var bottomRadius = 150000.0; var modelMatrix = Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( - ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-70.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, 100000.0)); + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-70.0, 45.0))), + new Cesium.Cartesian3(0.0, 0.0, 100000.0)); var cylinderInstance = new Cesium.GeometryInstance({ geometry : new Cesium.CylinderGeometry({ length : length, @@ -328,11 +326,11 @@ for (i = 0; i < 5; ++i) { height = 100000.0 + (200000.0 * i); boxModelMatrix = Cesium.Matrix4.multiplyByUniformScale(Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( - ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-106.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-106.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); ellipsoidModelMatrix = Cesium.Matrix4.multiplyByUniformScale(Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( - ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-102.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-102.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); sphereModelMatrix = Cesium.Matrix4.multiplyByUniformScale(Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( - ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-98.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-98.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); instances.push(new Cesium.GeometryInstance({ geometry : boxGeometry, @@ -593,7 +591,7 @@ topRadius = 0.0; bottomRadius = 200000.0; modelMatrix = Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( - ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-70.0, 40.0))), new Cesium.Cartesian3(0.0, 0.0, 200000.0)); + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-70.0, 40.0))), new Cesium.Cartesian3(0.0, 0.0, 200000.0)); cylinderInstance = new Cesium.GeometryInstance({ geometry : new Cesium.CylinderGeometry({ length : length, @@ -680,12 +678,11 @@ for (i = 0; i < 5; ++i) { height = 100000.0 + (200000.0 * i); boxModelMatrix = Cesium.Matrix4.multiplyByUniformScale(Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( - ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-108.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-108.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); ellipsoidModelMatrix = Cesium.Matrix4.multiplyByUniformScale(Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( - ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-104.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-104.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); sphereModelMatrix = Cesium.Matrix4.multiplyByUniformScale(Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame( - ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-100.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); - + ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(-100.0, 45.0))), new Cesium.Cartesian3(0.0, 0.0, height)), 90000.0); instances.push(new Cesium.GeometryInstance({ geometry : boxGeometry, modelMatrix : boxModelMatrix, @@ -718,7 +715,7 @@ closed : true }) })); - + positions = []; var colors = []; for (i = 0; i < 40; ++i) { @@ -745,7 +742,7 @@ } }) })); - + // create a polyline with a material positions = []; for (i = 0; i < 40; ++i) { @@ -764,7 +761,7 @@ material : Cesium.Material.fromType(scene.getContext(), Cesium.Material.PolylineGlowType) }) })); - + // create a polyline with per segment colors positions = []; colors = []; @@ -784,7 +781,7 @@ }), appearance : new Cesium.PolylineColorAppearance() })); - + // create a polyline with per vertex colors positions = []; colors = []; @@ -926,7 +923,7 @@ var polylineVolumeFill = new Cesium.GeometryInstance({ geometry : new Cesium.PolylineVolumeGeometry({ polylinePositions : positions, - vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, + vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, shapePositions : starPositions(7, 30000.0, 20000.0) }), attributes : { @@ -951,7 +948,7 @@ Cesium.Cartographic.fromDegrees(-105.0, 20.0), Cesium.Cartographic.fromDegrees(-110.0, 20.0) ]), - vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, + vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT, shapePositions : starPositions(7, 30000, 20000) }), attributes : { @@ -990,7 +987,7 @@ depthTest : { enabled : true }, - lineWidth : Math.min(1.0, scene.getContext().getMaximumAliasedLineWidth()) + lineWidth : 1.0 } }) })); diff --git a/Source/Core/PolylineVolumeGeometry.js b/Source/Core/PolylineVolumeGeometry.js index 237e036d51d9..6c3780d467bd 100644 --- a/Source/Core/PolylineVolumeGeometry.js +++ b/Source/Core/PolylineVolumeGeometry.js @@ -62,37 +62,37 @@ define([ var indicesCount = (length - 1) * (shapeLength) * 6 + firstEndIndices.length * 2; var indices = IndexDatatype.createTypedArray(vertexCount, indicesCount); var i, j; - var LL, UL, UR, LR; + var ll, ul, ur, lr; var offset = shapeLength * 2; var index = 0; for (i = 0; i < length - 1; i++) { for (j = 0; j < shapeLength - 1; j++) { - LL = j * 2 + i * shapeLength * 2; - LR = LL + offset; - UL = LL + 1; - UR = UL + offset; - - indices[index++] = UL; - indices[index++] = LL; - indices[index++] = UR; - indices[index++] = UR; - indices[index++] = LL; - indices[index++] = LR; + ll = j * 2 + i * shapeLength * 2; + lr = ll + offset; + ul = ll + 1; + ur = ul + offset; + + indices[index++] = ul; + indices[index++] = ll; + indices[index++] = ur; + indices[index++] = ur; + indices[index++] = ll; + indices[index++] = lr; } - LL = shapeLength * 2 - 2 + i * shapeLength * 2; - UL = LL + 1; - UR = UL + offset; - LR = LL + offset; - - indices[index++] = UL; - indices[index++] = LL; - indices[index++] = UR; - indices[index++] = UR; - indices[index++] = LL; - indices[index++] = LR; + ll = shapeLength * 2 - 2 + i * shapeLength * 2; + ul = ll + 1; + ur = ul + offset; + lr = ll + offset; + + indices[index++] = ul; + indices[index++] = ll; + indices[index++] = ur; + indices[index++] = ur; + indices[index++] = ll; + indices[index++] = lr; } - if (vertexFormat.st) { + if (vertexFormat.st || vertexFormat.tangent || vertexFormat.binormal) { // st required for tangent/binormal calculation var st = new Float32Array(vertexCount * 2); var lengthSt = 1 / (length - 1); var heightSt = 1 / (boundingRectangle.height); @@ -162,6 +162,15 @@ define([ if (vertexFormat.tangent || vertexFormat.binormal) { geometry = GeometryPipeline.computeBinormalAndTangent(geometry); + if (!vertexFormat.tangent) { + geometry.attributes.tangent = undefined; + } + if (!vertexFormat.binormal) { + geometry.attributes.binormal = undefined; + } + if (!vertexFormat.st) { + geometry.attributes.st = undefined; + } } return geometry; @@ -173,8 +182,8 @@ define([ * @alias PolylineVolumeGeometry * @constructor * - * @param {Array} options.polylinePositions An array of {Cartesain3} positions that define the center of the polyline volume. - * @param {Number} options.shapePositions An array of {Cartesian2} positions that define the shape to be extruded along the polyline + * @param {Array} options.polylinePositions An array of {@link Cartesain3} positions that define the center of the polyline volume. + * @param {Number} options.shapePositions An array of {@link Cartesian2} positions that define the shape to be extruded along the polyline * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference. * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer. * @param {Number} [options.height=0] The distance between the ellipsoid surface and the positions. @@ -210,7 +219,7 @@ define([ this._positions = positions; this._shape = shape; this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84); - this._height = defaultValue(options.height, 0); + this._height = defaultValue(options.height, 0.0); this._cornerType = defaultValue(options.cornerType, CornerType.ROUNDED); this._vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT); this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE); diff --git a/Source/Core/PolylineVolumeGeometryLibrary.js b/Source/Core/PolylineVolumeGeometryLibrary.js index 7711b78bc07f..2549c34c3a10 100644 --- a/Source/Core/PolylineVolumeGeometryLibrary.js +++ b/Source/Core/PolylineVolumeGeometryLibrary.js @@ -45,16 +45,19 @@ define([ var scratch1 = new Cartesian3(); var scratch2 = new Cartesian3(); + /** + * @private + */ var PolylineVolumeGeometryLibrary = {}; var cartographic = new Cartographic(); function scaleToSurface(positions, ellipsoid) { var heights = new Array(positions.length); - for(var i = 0; i < positions.length; i++) { + for ( var i = 0; i < positions.length; i++) { var pos = positions[i]; cartographic = ellipsoid.cartesianToCartographic(pos, cartographic); heights[i] = cartographic.height; - positions[i] = ellipsoid.scaleToGeodeticSurface(pos); + positions[i] = ellipsoid.scaleToGeodeticSurface(pos, pos); } return heights; } @@ -63,7 +66,7 @@ define([ var p0 = points[0]; var p1 = points[1]; var angleBetween = Cartesian3.angleBetween(p0, p1); - var numPoints = Math.ceil(angleBetween/granularity); + var numPoints = Math.ceil(angleBetween / granularity); var heights = new Array(numPoints); var i; if (h0 === h1) { @@ -78,7 +81,7 @@ define([ var heightPerVertex = dHeight / (numPoints); for (i = 1; i < numPoints; i++) { - var h = h0 + i*heightPerVertex; + var h = h0 + i * heightPerVertex; heights[i] = h; } @@ -140,7 +143,7 @@ define([ function addPositions(centers, left, shape, finalPositions, ellipsoid, heights, xScalar) { for ( var i = 0; i < centers.length; i += 3) { var center = Cartesian3.fromArray(centers, i, centerScratch); - finalPositions = addPosition(center, left, shape, finalPositions, ellipsoid, heights[i/3], xScalar, 1); + finalPositions = addPosition(center, left, shape, finalPositions, ellipsoid, heights[i / 3], xScalar, 1); } return finalPositions; } @@ -243,9 +246,6 @@ define([ return finalPositions; } - /** - * @private - */ PolylineVolumeGeometryLibrary.removeDuplicates = function(shapePositions) { var length = shapePositions.length; var cleanedPositions = []; @@ -276,9 +276,6 @@ define([ return ((prev.x * next.y) - (prev.y * next.x)) >= 0.0; }; - /** - * @private - */ PolylineVolumeGeometryLibrary.computePositions = function(positions, shape2D, boundingRectangle, geometry, duplicatePoints) { var ellipsoid = geometry._ellipsoid; var heights = scaleToSurface(positions, ellipsoid); @@ -335,7 +332,7 @@ define([ var scalar = 1 / Math.max(0.25, (Cartesian3.magnitude(Cartesian3.cross(cornerDirection, backward, scratch1)))); var leftIsOutside = PolylineVolumeGeometryLibrary.angleIsGreaterThanPi(forward, backward, position, ellipsoid); if (leftIsOutside) { - pivot = Cartesian3.add(position, Cartesian3.multiplyByScalar(cornerDirection, scalar*width, cornerDirection), pivot); + pivot = Cartesian3.add(position, Cartesian3.multiplyByScalar(cornerDirection, scalar * width, cornerDirection), pivot); start = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, width, start), start); scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]); scratch2Array[1] = Cartesian3.clone(start, scratch2Array[1]); @@ -353,7 +350,7 @@ define([ } previousPosition = Cartesian3.clone(end, previousPosition); } else { - pivot = Cartesian3.add(position, Cartesian3.multiplyByScalar(cornerDirection, scalar*width, cornerDirection), pivot); + pivot = Cartesian3.add(position, Cartesian3.multiplyByScalar(cornerDirection, scalar * width, cornerDirection), pivot); start = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, -width, start), start); scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]); scratch2Array[1] = Cartesian3.clone(start, scratch2Array[1]); diff --git a/Specs/Core/PolylineVolumeGeometrySpec.js b/Specs/Core/PolylineVolumeGeometrySpec.js index 1965b82efb07..a62cea5b6bcd 100644 --- a/Specs/Core/PolylineVolumeGeometrySpec.js +++ b/Specs/Core/PolylineVolumeGeometrySpec.js @@ -159,7 +159,7 @@ defineSuite([ var corners = 90/5 * 2; expect(m.attributes.position.values.length).toEqual(3 * (corners * 4 * 2 * 2 + 4 * 2 * 9)); - expect(m.indices.length).toEqual(3 * (corners * 4 * 2 * 2 + 4 * 7 * 2 + 4)); + expect(m.indices.length).toEqual(3 * (corners * 4 * 2 * 2 + 4 * 7 * 2 + 4)); }); it('computes with beveled corners', function() { @@ -177,6 +177,6 @@ defineSuite([ })); expect(m.attributes.position.values.length).toEqual(3 * (2 * 8 + 4 * 2 * 9)); - expect(m.indices.length).toEqual(3 * (8 * 2 + 4 * 7 * 2 + 4)); + expect(m.indices.length).toEqual(3 * (8 * 2 + 4 * 7 * 2 + 4)); }); }); From ced6b5f97d3acc7e5b6c8fa65ff05a40bf28734b Mon Sep 17 00:00:00 2001 From: hpinkos Date: Fri, 27 Sep 2013 12:08:08 -0400 Subject: [PATCH 15/21] back out cesium viewer changes --- Apps/CesiumViewer/CesiumViewer.js | 177 +----------------------------- 1 file changed, 3 insertions(+), 174 deletions(-) diff --git a/Apps/CesiumViewer/CesiumViewer.js b/Apps/CesiumViewer/CesiumViewer.js index f00bf0202161..b92c90c373a6 100644 --- a/Apps/CesiumViewer/CesiumViewer.js +++ b/Apps/CesiumViewer/CesiumViewer.js @@ -1,23 +1,5 @@ /*global define*/ -define(['Core/CorridorGeometry', - 'Core/Cartographic', - 'Core/ColorGeometryInstanceAttribute', - 'Core/Color', - 'Core/CornerType', - 'Core/Math', - 'Core/PolylineVolumeGeometry', - 'Core/PolylineVolumeOutlineGeometry', - 'Core/Extent', - 'Core/ExtentGeometry', - 'Core/Cartesian2', - 'Core/ExtentOutlineGeometry', - 'Core/EllipseGeometry', - 'Core/GeometryInstance', - 'Core/VertexFormat', - 'Scene/createTangentSpaceDebugPrimitive', - 'Scene/DebugAppearance', - 'Scene/PerInstanceColorAppearance', - 'Scene/Primitive', +define([ 'Core/defined', 'DynamicScene/CzmlDataSource', 'DynamicScene/GeoJsonDataSource', @@ -28,25 +10,6 @@ define(['Core/CorridorGeometry', 'Widgets/Viewer/viewerDynamicObjectMixin', 'domReady!' ], function( - CorridorGeometry, - Cartographic, - ColorGeometryInstanceAttribute, - Color, - CornerType, - CesiumMath, - PolylineVolumeGeometry, - PolylineVolumeOutlineGeometry, - Extent, - ExtentGeometry, - Cartesian2, - ExtentOutlineGeometry, - EllipseGeometry, - GeometryInstance, - VertexFormat, - createTangentSpaceDebugPrimitive, - DebugAppearance, - PerInstanceColorAppearance, - Primitive, defined, CzmlDataSource, GeoJsonDataSource, @@ -144,7 +107,7 @@ define(['Core/CorridorGeometry', viewer.dataSources.add(source); if (defined(endUserOptions.lookAt)) { - var dynamicObject = source.getDynamicObjectCollection().getObject(endUserOptions.lookAt); + var dynamicObject = source.getDynamicObjectCollection().getById(endUserOptions.lookAt); if (defined(dynamicObject)) { viewer.trackedObject = dynamicObject; } else { @@ -177,139 +140,5 @@ define(['Core/CorridorGeometry', console.error(error); } } - - var primitives = scene.getPrimitives(); - var ellipsoid = viewer.centralBody.getEllipsoid(); - - // var solidWhite = new ColorGeometryInstanceAttribute(1.0, 1.0, 1.0, 1.0); - - /* var positions = ellipsoid.cartographicArrayToCartesianArray([ - Cartographic.fromDegrees(-89.0, -3.0), - Cartographic.fromDegrees(-89.0, -2.0), - Cartographic.fromDegrees(-90.0, -2.0), - Cartographic.fromDegrees(-90.0, -1.0), - Cartographic.fromDegrees(-90.0, 0.0) - - ]); - */ -// var positions = ellipsoid.cartographicArrayToCartesianArray([ - // Cartographic.fromDegrees(-120.0, 40.0), - // Cartographic.fromDegrees(-120.0, 50.0) - // ]); - - var positions = ellipsoid.cartographicArrayToCartesianArray([ - // Cartographic.fromDegrees(-85.0, 35.0, 0), - // Cartographic.fromDegrees(-85.0, 36.0, 0), - Cartographic.fromDegrees(-87.0, 38.0), - // Cartographic.fromDegrees(-86, 39.0), - Cartographic.fromDegrees(-90.0, 40.0) - ]); - - function ellipsePositions(horizontalRadius, verticalRadius) { - var pos = []; - var theta = CesiumMath.toRadians(1); - var posCount = Math.PI*2/theta; - for (var i = 0; i < posCount; i++) { - pos.push(new Cartesian2(horizontalRadius * Math.cos(theta * i), verticalRadius * Math.sin(theta * i))); - } - return pos; - } - - - function starPositions(arms, rOuter, rInner) { - var angle = Math.PI / arms; - - var pos = []; - - for (var i = 0; i < 2 * arms; i++) { - var r = (i % 2) === 0 ? rOuter : rInner; - var p = new Cartesian2(Math.cos(i * angle) * r, Math.sin(i * angle) * r); - pos.push(p); - } - return pos; - } - - function boxPositions() { - return [new Cartesian2(-50000, -50000), new Cartesian2(50000, -50000), new Cartesian2(50000, 50000), new Cartesian2(-50000, 50000)]; - } - - var geo = PolylineVolumeGeometry.createGeometry(new PolylineVolumeGeometry({ - polylinePositions : positions, - vertexFormat: VertexFormat.ALL, - // shapePositions: boxPositions(), - // shapePositions: starPositions(7, 20000, 10000), - shapePositions: ellipsePositions(40000, 40000), - cornerType: CornerType.MITERED - })); - - var volume = new GeometryInstance({ - geometry: geo, - attributes: { - color : ColorGeometryInstanceAttribute.fromColor(Color.fromRandom({alpha : 1.0})) - } - }); - - var p = new Primitive({ - geometryInstances: [volume], - appearance : new DebugAppearance({ - attributeName: 'st' - })/*new PerInstanceColorAppearance({ - translucent : false, - closed : true - })*/ - }); - - primitives.add(p); -/* - var outlineGeo = new PolylineVolumeOutlineGeometry({ - polylinePositions : positions, - shapePositions: boxPositions(), - // shapePositions: starPositions(7, 20000, 10000), - // shapePositions: ellipsePositions(40000, 40000), - cornerType: CornerType.MITERED - }); - - var inst = new GeometryInstance({ - geometry: PolylineVolumeOutlineGeometry.createGeometry(outlineGeo), - attributes : { - color : solidWhite - } - }); - - primitives.add(new Primitive({ - geometryInstances: inst, - appearance : new PerInstanceColorAppearance({ - flat : true, - renderState : { - depthTest : { - enabled : true - }, - lineWidth : Math.min(4.0, scene.getContext().getMaximumAliasedLineWidth()) - } - }) - })); -*/ - var debugp = createTangentSpaceDebugPrimitive({ - geometry : geo - }); - primitives.add(debugp); - - - - /* - primitives.add(createTangentSpaceDebugPrimitive({ - geometry: airspaceGeo - })); - var drawCommand = new DrawCommand(); - drawCommand.owner = p; - p.command = drawCommand; - - scene.debugCommandFilter = function(command) { - if (command.owner === p) { - command.boundingVolume = p._boundingSphere; - command.debugShowBoundingVolume = true; - } - return true; - };*/ } -}); +}); \ No newline at end of file From b1970d52036776d6f34b132db5385dd3fc1d9140 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Fri, 27 Sep 2013 14:08:15 -0400 Subject: [PATCH 16/21] Sandcastle example --- .../gallery/Polyline Volume Outline.html | 142 ++++++++++++++++++ .../gallery/Polyline Volume Outline.jpg | Bin 0 -> 41124 bytes Apps/Sandcastle/gallery/Polyline Volume.html | 119 +++++++++++++++ Apps/Sandcastle/gallery/Polyline Volume.jpg | Bin 0 -> 31817 bytes 4 files changed, 261 insertions(+) create mode 100644 Apps/Sandcastle/gallery/Polyline Volume Outline.html create mode 100644 Apps/Sandcastle/gallery/Polyline Volume Outline.jpg create mode 100644 Apps/Sandcastle/gallery/Polyline Volume.html create mode 100644 Apps/Sandcastle/gallery/Polyline Volume.jpg diff --git a/Apps/Sandcastle/gallery/Polyline Volume Outline.html b/Apps/Sandcastle/gallery/Polyline Volume Outline.html new file mode 100644 index 000000000000..404a1456d1dc --- /dev/null +++ b/Apps/Sandcastle/gallery/Polyline Volume Outline.html @@ -0,0 +1,142 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Polyline Volume Outline.jpg b/Apps/Sandcastle/gallery/Polyline Volume Outline.jpg new file mode 100644 index 0000000000000000000000000000000000000000..887c643052ea5e2adbd2ee9dcbe34c5b04add557 GIT binary patch literal 41124 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 + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Polyline Volume.jpg b/Apps/Sandcastle/gallery/Polyline Volume.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e1bf23942f4a51d0215acb604b7842c3a320199b GIT binary patch literal 31817 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

IEpOF=tsS?Mr&4fXuLyP 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

wR zS)rYyyLLju56e1T$t9SlB!^%krfEGQ@gLXOaOTaUH+h^m(D(dz27f;kCMfnf`sjq` f5;@9V!{LTz Date: Wed, 2 Oct 2013 12:22:58 -0400 Subject: [PATCH 17/21] cleanup --- CHANGES.md | 6 +++--- Source/Core/PolylineVolumeGeometry.js | 4 ++-- Source/Core/PolylineVolumeGeometryLibrary.js | 14 ++------------ Source/Core/PolylineVolumeOutlineGeometry.js | 4 ++-- Source/Core/Shapes.js | 8 ++++---- 5 files changed, 13 insertions(+), 23 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 023e1251da1f..2c053fda90ea 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,9 @@ Change Log Beta Releases ------------- +### b22 - 2013-11-101 +* Added `PolylineVolumeGeometry` and `PolylineVolumeGeometryOutline`. +* Added `Shapes.compute2DCircle`. ### b21 - 2013-10-01 @@ -66,9 +69,6 @@ Beta Releases * Fixed geometries not closing completely. [#1093](https://github.com/AnalyticalGraphicsInc/cesium/issues/1093) * Fixed `EllipsoidTangentPlane.projectPointOntoPlane` for tangent planes on an ellipsoid other than the unit sphere. * `CompositePrimitive.add` now returns the added primitive. This allows us to write more concise code. -* Added `PolylineVolumeGeometry` and `PolylineVolumeGeometryOutline`. -* Added `Shapes.compute2DCircle`. - var p = new Primitive(/* ... */); primitives.add(p); return p; diff --git a/Source/Core/PolylineVolumeGeometry.js b/Source/Core/PolylineVolumeGeometry.js index 6c3780d467bd..0af2b21c00ff 100644 --- a/Source/Core/PolylineVolumeGeometry.js +++ b/Source/Core/PolylineVolumeGeometry.js @@ -190,8 +190,8 @@ define([ * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed. * @param {Boolean} [options.cornerType = CornerType.ROUNDED] Determines the style of the corners. * - * @exception {DeveloperError} options.positions is required. - * @exception {DeveloperError} options.width is required. + * @exception {DeveloperError} options.polylinePositions is required. + * @exception {DeveloperError} options.shapePositions is required. * * @see PolylineVolumeGeometry#createGeometry * diff --git a/Source/Core/PolylineVolumeGeometryLibrary.js b/Source/Core/PolylineVolumeGeometryLibrary.js index 2549c34c3a10..9f07c8a6fec0 100644 --- a/Source/Core/PolylineVolumeGeometryLibrary.js +++ b/Source/Core/PolylineVolumeGeometryLibrary.js @@ -92,12 +92,8 @@ define([ function computeRotationAngle(start, end, position, ellipsoid) { var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid); - var origin = tangentPlane.projectPointOntoPlane(position, originScratch); var next = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, start, nextScratch), nextScratch); var prev = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, end, prevScratch), prevScratch); - prev = Cartesian2.subtract(prev, origin, prev); - next = Cartesian2.subtract(next, origin, next); - var angle = Cartesian2.angleBetween(next, prev); return (prev.x * next.y - prev.y * next.x >= 0.0) ? -angle : angle; @@ -108,8 +104,8 @@ define([ var translation = new Matrix4(); var rotationZ = new Matrix3(); var scaleMatrix = Matrix3.IDENTITY.clone(); - var westScratch = new Cartesian3(); - var finalPosScratch = new Cartesian3(); + var westScratch = new Cartesian4(); + var finalPosScratch = new Cartesian4(); var heightCartesian = new Cartesian3(); function addPosition(center, left, shape, finalPositions, ellipsoid, height, xScalar, repeat) { var west = westScratch; @@ -117,7 +113,6 @@ define([ transform = Transforms.eastNorthUpToFixedFrame(center, ellipsoid, transform); west = Matrix4.multiplyByVector(transform, negativeX, west); - west = Cartesian3.fromCartesian4(west, west); west = Cartesian3.normalize(west, west); var angle = computeRotationAngle(west, left, center, ellipsoid); rotationZ = Matrix3.fromRotationZ(angle, rotationZ); @@ -261,18 +256,13 @@ define([ return cleanedPositions; }; - var originScratch = new Cartesian3(); var nextScratch = new Cartesian3(); var prevScratch = new Cartesian3(); PolylineVolumeGeometryLibrary.angleIsGreaterThanPi = function(forward, backward, position, ellipsoid) { var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid); - var origin = tangentPlane.projectPointOntoPlane(position, originScratch); var next = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, forward, nextScratch), nextScratch); var prev = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, backward, prevScratch), prevScratch); - prev = Cartesian2.subtract(prev, origin, prev); - next = Cartesian2.subtract(next, origin, next); - return ((prev.x * next.y) - (prev.y * next.x)) >= 0.0; }; diff --git a/Source/Core/PolylineVolumeOutlineGeometry.js b/Source/Core/PolylineVolumeOutlineGeometry.js index cee6ffdee115..9c8be4332e07 100644 --- a/Source/Core/PolylineVolumeOutlineGeometry.js +++ b/Source/Core/PolylineVolumeOutlineGeometry.js @@ -107,8 +107,8 @@ define([ * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer. * @param {Boolean} [options.cornerType = CornerType.ROUNDED] Determines the style of the corners. * - * @exception {DeveloperError} options.positions is required. - * @exception {DeveloperError} options.width is required. + * @exception {DeveloperError} options.polylinePositions is required. + * @exception {DeveloperError} options.shapePositions is required. * * @see PolylineVolumeOutlineGeometry#createGeometry * diff --git a/Source/Core/Shapes.js b/Source/Core/Shapes.js index 0c7f15f431a9..9d6a337f7986 100644 --- a/Source/Core/Shapes.js +++ b/Source/Core/Shapes.js @@ -235,16 +235,16 @@ define([ * Computes a 2D circle about the origin. * * @param {Number} [radius = 1.0] The radius of the circle + * @param {Number} [granularity = Cesium.RADIANS_PER_DEGREE*2] The radius of the circle * * @returns The set of points that form the ellipse's boundary. * * @example * var circle = Shapes.compute2DCircle(100000.0); */ - compute2DCircle : function(radius) { - if (!defined(radius)) { - radius = 1.0; - } + compute2DCircle : function(radius, granularity) { + radius = defaultValue(radius, 1.0); + granularity = defaultValue(granularity, CesiumMath.RADIANS_PER_DEGREE*2); var positions = []; var theta = CesiumMath.toRadians(1.0); var posCount = Math.PI*2/theta; From b6256c5df85a2700d03f0a75a8f92d8f46b306bb Mon Sep 17 00:00:00 2001 From: hpinkos Date: Fri, 4 Oct 2013 14:16:52 -0400 Subject: [PATCH 18/21] handle duplicate positions with different heights --- Source/Core/PolylineVolumeGeometry.js | 6 ++-- Source/Core/PolylineVolumeGeometryLibrary.js | 33 +++++++++++++++++++- Source/Core/PolylineVolumeOutlineGeometry.js | 6 ++-- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/Source/Core/PolylineVolumeGeometry.js b/Source/Core/PolylineVolumeGeometry.js index 0af2b21c00ff..2e418177ae0a 100644 --- a/Source/Core/PolylineVolumeGeometry.js +++ b/Source/Core/PolylineVolumeGeometry.js @@ -11,7 +11,6 @@ define([ './IndexDatatype', './Math', './PolygonPipeline', - './PolylinePipeline', './PolylineVolumeGeometryLibrary', './PrimitiveType', './defaultValue', @@ -33,7 +32,6 @@ define([ IndexDatatype, CesiumMath, PolygonPipeline, - PolylinePipeline, PolylineVolumeGeometryLibrary, PrimitiveType, defaultValue, @@ -240,12 +238,12 @@ define([ var brScratch = new BoundingRectangle(); PolylineVolumeGeometry.createGeometry = function(polylineVolumeGeometry) { var positions = polylineVolumeGeometry._positions; - var cleanPositions = PolylinePipeline.removeDuplicates(positions); + var cleanPositions = PolylineVolumeGeometryLibrary.removeDuplicatesFromPositions(positions, polylineVolumeGeometry._ellipsoid); if (cleanPositions.length < 2) { throw new DeveloperError('Count of unique polyline positions must be greater than 1.'); } var shape2D = polylineVolumeGeometry._shape; - shape2D = PolylineVolumeGeometryLibrary.removeDuplicates(shape2D); + shape2D = PolylineVolumeGeometryLibrary.removeDuplicatesFromShape(shape2D); if (shape2D.length < 3) { throw new DeveloperError('Count of unique shape positions must be at least 3.'); } diff --git a/Source/Core/PolylineVolumeGeometryLibrary.js b/Source/Core/PolylineVolumeGeometryLibrary.js index 9f07c8a6fec0..a5960cb31abb 100644 --- a/Source/Core/PolylineVolumeGeometryLibrary.js +++ b/Source/Core/PolylineVolumeGeometryLibrary.js @@ -241,7 +241,7 @@ define([ return finalPositions; } - PolylineVolumeGeometryLibrary.removeDuplicates = function(shapePositions) { + PolylineVolumeGeometryLibrary.removeDuplicatesFromShape = function(shapePositions) { var length = shapePositions.length; var cleanedPositions = []; for ( var i0 = length - 1, i1 = 0; i1 < length; i0 = i1++) { @@ -266,6 +266,34 @@ define([ return ((prev.x * next.y) - (prev.y * next.x)) >= 0.0; }; + function latLonEquals(c0, c1) { + return ((CesiumMath.equalsEpsilon(c0.latitude, c1.latitude, CesiumMath.EPSILON6)) && (CesiumMath.equalsEpsilon(c0.longitude, c1.longitude, CesiumMath.EPSILON6))); + } + var carto0 = new Cartographic(); + var carto1 = new Cartographic(); + PolylineVolumeGeometryLibrary.removeDuplicatesFromPositions = function(positions, ellipsoid) { + var length = positions.length; + if (length < 2) { + return positions.slice(0); + } + + var cleanedPositions = []; + cleanedPositions.push(positions[0]); + + for (var i = 1; i < length; ++i) { + var v0 = positions[i - 1]; + var v1 = positions[i]; + var c0 = ellipsoid.cartesianToCartographic(v0, carto0); + var c1 = ellipsoid.cartesianToCartographic(v1, carto1); + + if (!latLonEquals(c0, c1)) { + cleanedPositions.push(v1); // Shallow copy! + } + } + + return cleanedPositions; + }; + PolylineVolumeGeometryLibrary.computePositions = function(positions, shape2D, boundingRectangle, geometry, duplicatePoints) { var ellipsoid = geometry._ellipsoid; var heights = scaleToSurface(positions, ellipsoid); @@ -358,6 +386,9 @@ define([ previousPosition = Cartesian3.clone(end, previousPosition); } backward = Cartesian3.negate(forward, backward); + } else { + finalPositions = addPosition(previousPosition, left, shapeForSides, finalPositions, ellipsoid, h0 + heightOffset, 1, 1); + previousPosition = position; } h0 = h1; h1 = heights[i + 1]; diff --git a/Source/Core/PolylineVolumeOutlineGeometry.js b/Source/Core/PolylineVolumeOutlineGeometry.js index 9c8be4332e07..28fd389e53d8 100644 --- a/Source/Core/PolylineVolumeOutlineGeometry.js +++ b/Source/Core/PolylineVolumeOutlineGeometry.js @@ -11,7 +11,6 @@ define([ './IndexDatatype', './Math', './PolygonPipeline', - './PolylinePipeline', './PolylineVolumeGeometryLibrary', './PrimitiveType', './defaultValue', @@ -32,7 +31,6 @@ define([ IndexDatatype, CesiumMath, PolygonPipeline, - PolylinePipeline, PolylineVolumeGeometryLibrary, PrimitiveType, defaultValue, @@ -154,12 +152,12 @@ define([ var brScratch = new BoundingRectangle(); PolylineVolumeOutlineGeometry.createGeometry = function(polylineVolumeOutlineGeometry) { var positions = polylineVolumeOutlineGeometry._positions; - var cleanPositions = PolylinePipeline.removeDuplicates(positions); + var cleanPositions = PolylineVolumeGeometryLibrary.removeDuplicatesFromPositions(positions, polylineVolumeOutlineGeometry._ellipsoid); if (cleanPositions.length < 2) { throw new DeveloperError('Count of unique polyline positions must be greater than 1.'); } var shape2D = polylineVolumeOutlineGeometry._shape; - shape2D = PolylineVolumeGeometryLibrary.removeDuplicates(shape2D); + shape2D = PolylineVolumeGeometryLibrary.removeDuplicatesFromShape(shape2D); if (shape2D.length < 3) { throw new DeveloperError('Count of unique shape positions must be at least 3.'); } From 93eac489892267808b9a33ff3ec898a50a81ff29 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Fri, 4 Oct 2013 14:20:48 -0400 Subject: [PATCH 19/21] fix CHANGES.md --- CHANGES.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 432ca6da7403..5ae2d2f07696 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,10 +3,6 @@ Change Log Beta Releases ------------- -### b22 - 2013-11-101 -* Added `PolylineVolumeGeometry` and `PolylineVolumeGeometryOutline`. -* Added `Shapes.compute2DCircle`. - ### b22 - 2013-11-01 * Breaking changes: @@ -34,6 +30,8 @@ Beta Releases // Use properties and functions in p.id } * Added `Scene.drillPick` to return list of objects each containing 1 primitive at a screen space position. +* Added `PolylineVolumeGeometry` and `PolylineVolumeGeometryOutline`. +* Added `Shapes.compute2DCircle`. ### b21 - 2013-10-01 From 7ae22ebe48f9f212f2c688736457fd5ce90278e1 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Fri, 4 Oct 2013 14:22:26 -0400 Subject: [PATCH 20/21] fix CHANGES.md --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 5ae2d2f07696..fce57229fb63 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,7 @@ Change Log Beta Releases ------------- + ### b22 - 2013-11-01 * Breaking changes: @@ -95,6 +96,7 @@ Beta Releases * Fixed geometries not closing completely. [#1093](https://github.com/AnalyticalGraphicsInc/cesium/issues/1093) * Fixed `EllipsoidTangentPlane.projectPointOntoPlane` for tangent planes on an ellipsoid other than the unit sphere. * `CompositePrimitive.add` now returns the added primitive. This allows us to write more concise code. + var p = new Primitive(/* ... */); primitives.add(p); return p; From 680f789219f8a767bb6c9be6790fd3077d15d803 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Fri, 4 Oct 2013 14:47:25 -0400 Subject: [PATCH 21/21] swap texture coordinates --- Source/Core/PolylineVolumeGeometry.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Source/Core/PolylineVolumeGeometry.js b/Source/Core/PolylineVolumeGeometry.js index 2e418177ae0a..70d315be8055 100644 --- a/Source/Core/PolylineVolumeGeometry.js +++ b/Source/Core/PolylineVolumeGeometry.js @@ -98,30 +98,30 @@ define([ var s, t; var stindex = 0; for (i = 0; i < length; i++) { - t = i * lengthSt; - s = heightSt * (shape[0].y + heightOffset); + s = i * lengthSt; + t = heightSt * (shape[0].y + heightOffset); st[stindex++] = s; st[stindex++] = t; for (j = 1; j < shapeLength; j++) { - s = heightSt * (shape[j].y + heightOffset); + t = heightSt * (shape[j].y + heightOffset); st[stindex++] = s; st[stindex++] = t; st[stindex++] = s; st[stindex++] = t; } - s = heightSt * (shape[0].y + heightOffset); + t = heightSt * (shape[0].y + heightOffset); st[stindex++] = s; st[stindex++] = t; } for (j = 0; j < shapeLength; j++) { - t = 0; - s = heightSt * (shape[j].y + heightOffset); + s = 0; + t = heightSt * (shape[j].y + heightOffset); st[stindex++] = s; st[stindex++] = t; } for (j = 0; j < shapeLength; j++) { - t = (length - 1) * lengthSt; - s = heightSt * (shape[j].y + heightOffset); + s = (length - 1) * lengthSt; + t = heightSt * (shape[j].y + heightOffset); st[stindex++] = s; st[stindex++] = t; }