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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
+
+
+
+
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
zcmbrkcQjmY^gcR*=n=gWy^T%~6C!%=y+!XvFCip)FCz$1qDSvt2xdkndM`7KglI#;
zh<1IxcdhUJ=l*fm{k>=HbIv;DJ^MNDI%hxo+3)@G{WjpKy0V%w01FEX@aACw+#>)k
zO2Mv90DzVjfDZrw-~({5=mEG7dsq)9i^cH2`^s260PO!c{^;SP2;gB2Ab&9J2m7zR
zd9eTP`Jb(>t*e*6pth3Xvp_LH2|*EI!2KFP;lJnpAGQBF`9JF5znkyB1IX|J*#HDK
z78~FZ85TAf);$!!^58x0|K0Zki2rF=kFarYALHQ@5E4CH(DoGY2n!qg5e_ykE)LGa
z)$bn818~T2$yr6@A5-Yr;<5Qqihjr_#%F)sG(e?4bIKuR=Nm~tNKHdaN6*Q{&BMzl
zF7aGaO8SL@qLQ+Ts+ziip^>qPshPRGgQJtPi>sTTe?VYRaLC)J=$P2J_=H4A=Etn;
zoKLxVC8cHM6_sDBs+(I{+uA!iySfL5pfLFG$mrPY-2B4g((=kG0=ct`+WYxy{{Ve<
z{^#$-CFbhif4Hy!*#G;={$FvCJ>YtTgM*EO_a82-M?nvUO@@QZD)N|IUJuXKhk{M?
z13u;JjN+yN0(LR|Qz|>(8A56faRev&KWP7h?Ef3E$p2r+{ukK)gKHH)jE(g$c-UkB
zIlvvZdcDrrkL{F!k)S6D?MCSy>-1)18+zYWr)U7rkv)mnL*%aR%5tY|Dc|es0OwNt4g}Qq
z8a`#LKs%BteB}qMVZQ>WB#V{|v*hi_YLrxAMQJ?EFsz|+%CdMf$YUw|YMio~)N{t4
zQTeyjBlqB3R-2YmN;3S5Y-uACtJ%!1GC3E=jy&N>2EH|G8duutX;#tbnB7>~ofLgF
zhYV{YgS`+fJ{WzKtDu?s;|%+bxBI#C1dK|wV!hV3wx}yN#{>grho)Q>5$JEixK0kq
zJkuk80UW2Daj|L_^&GSX;;Eg^i~N>I`B1EpVYQ>&Bf(O8oirz&fVB7UkWaSCnl
zwk--6?phvATbx|oGL0A$+(oI0;_b>$rRl0qmM1*f
z8WXAgcp}B<&N@m`27?0D&7_h;E_S8BJlBWF69S@v+>8l
zR2`%uC?V?oeClL=)iUh^?UC&Y1N>UDsH<|SQ&=QhiDN0&CmbhRhA*TA2g7_l0KsoN
zMLaU=FdVF6qj;~U&@(NLhNsyIxzwmtA0pMNi&&`dYJT_O+9+i^KRL?Fu`5G9FVr54
zm%-@y6t_OhQ2adWyg{>33x7~X$7|2Sj4+v1Zjd`w3W?38Xp}|VOzSWfoi-NJHyd^$
zF|(3CDpt4S{j`)1FVDqTtN93`vk{V*TFuG(NV~!as-*u4uBMoP59pii2}gNFT?ya3
zH!rL7Q|s|RZfGZt(d9VI)M;?EUY92d&yy^s`k?(5{s+~WVyj2UO
zCddYPbvSjWFB9U0eoQXS^J|P!tOlsm@m#M%SS_5lGUY;i7Qf=D094L3osK;n4qm#Tiv^Gz1J)5nm{yCN1nDjb_`t@*sJjAcIB$(uImzaWECPTBJ2
zCQ44!vzi=S`fD3@q^KzV(f1?CSm!i!E8iWSOpQ`~@D|ufAn3!`M(?6V_qNL2~>*aqLmegUQHz@Nua%~ey9$#O`5&l{QU7zdi>
z**#KBeOA>cta@I+Fv+Yh5`>Rbh1SD36!u!Kl98Yx)RNs)AkB7H5TNnwbx+89xC7w`
zXizP~*Ia@yh+{^e!!c%d+ew;PZI(KZXA5^;Wv^%>Etgu^rDBDBz>oaXO^L<00Qhs4
z_N0}KVs`)Hj`>AIOFUIJkJ+s9a#NK(4(a1x1WgEg$dl{_jMLJQq(<@MwgAZjW
zIg79*Tglb=*atgi!>^fmpA@U#h-xr4=cKyQm$nx^bs%pHG|Z}N?6GH4R9yD7Pc_or
z&UH?Itfr`_d^o+Aq%W0G@IE^*)$^c(a87iRy%|26fP7=9Tw$av>B@H%?hqx$7TNqa
zaK%RF{4T?2!o^;dftN#1Ly+Ar^0%O-sM!04y{9uDJ6uX4xLr~fp19ZbihU&$jp|12
zM$=(ABu&_{^Y{CxF5@atdVEc_mWoP~GjMFNSQ%!j5i6gcx)jx+UwfGkBVXi5ZurMf
z^hr>Gzx99)PAHJBZYt
zLp&)0hoB_~F3j5tJgdwEOG!51xe<0b&n1(*IKCw^T+58?wsp;~I*
zMY?b}$}LLXP%&Ik-^R;1O|L{S{6bTSaw?g@*e(>~#xk1#uc8KFTh&olw`(>=-eR4mFn2Y^Ih~-
zYQ3%kcWRi0H|}KbuZ}y;#Vl&%?7`~ZnCa3H%&u_rjH*j5sj3-*^J$c_2^i%TP_8YS
zm~10yrYulX(Ot25seYlwE1>WE((Or|x|or(mcC~vthG8&f5EdsW#%~Tx5@?VHu)al
z1b!3_FO)zZ=UzX%0b(?&R*Iq)w}|^V_KO&0$-?v(`uG%(CwSw2$eu3GFkZwODUo=^
zECZNvrC~7F{0{avs%jv@b^Dg^9-!leeewDB(;cqW4Fg&Z_EHRy^PL5M-y359v!*#c
zjwuckz{n#)$4P9IXL{u$oWVgzFm7;AU=^}Uyhqxr_3Ahv`0w?;@hnmcY%srcnB6~K
z+b4~l+?DiM4?cnUT4;eef{ZgUpDk9zX~&J*c^}uWf~7mt6r1ILNNu@33uOJ<_)Y2_VE?jwZdHxydp^gL
zajtP!%OV0Xs4CPJ&q|5Gb7a@!++f&qYT8f%4txfUN0Uo?hNZgS@~%huITbWivR
z2$~9vzXt>-(@`}w4zq`NC6~9yjZNg9u6qK{;4P0VuRNX)nw6#llNdvVPe=LG<9qDF
zjg^+CyC;H#&cZPn*wU(gTq9fKn+^$LD7KmeZ@p~&LkCUjt4Bs{-QhN;8jd^8Ap}NO
zW;;Z!HBcNi@`+S@)<{}Zke+yL*kJSIGpDy2G#~YJ!Ci?`s1GJsiCWk|`3g=XpC^)a
zT=m+4pEaYP)cN~F#+pAw(m#52ggDaRF&r$4Fy0`humpV&@#&TtRbU4_ojQqhk<8_e
zRXYOpWJOYTQi~?#0*_Hp4m3C-k6?sfbyNMm#abuX1=hgd1Z@WR1_z
zBML0SL*iY+?MU1FL_xB$E?2tIh`-0RF&DWsQ-R=3RZeo1+}uT_*ST5mhk$PB+X+kH
zXU%-g?7Ul|&)IBb#?JQ6(oPESa%kTKj--AuySN8L?QiXJ&Agw@1yv;K@D8dXoO}E;-B%4d!(UMzkAY9`&a_)6bm$w
zCZeoHJbi|~aKg{oDhA9=Bp+$`Ja)UgTUCLYvsTLjWs^Y?<-M!ldexZrs)!p+-TbW0
z&27Go-YvAIZeE0vh^pDBx1=gVT{39S=8`GJ}cMfngXi+z~BK(Sl(Z*a
zXj(Lt$T{Bno|>OS%cDYVLnVvRKphKfBVN3rLAyUr>tCKej6lwTX48T1nW12Z%NE~`
zzpj$>XOA?-aF>j*5jHbAy(fPXy!<~mw5W-yLx{{!sfzB=u4N{sc|vS2?;Ijc;h0B*
zRWDv?dIHnel&{St{*3xDt-Cbiwfk`xgaB1leQguXPOKd@U;j9ak4x4egponqP3<|d
z#jiD~*Kk_2D4P`eC$f1K8KOlv2fJUfyOxD8s({&VE7nKS+y?h(NCcSv`MrA>ek|E8-Fq%Ylno4GtKKr996e`j(?N4DVcL5CipY;$kmDQkqG`h*Ed#4K}Lx38kHz}ms9vnp+UMjl+mw@
zUF@EpWd-JIaTx5`@&om0!{R4YSqpI=8wPe4%2b&v;A}ZZ>8}3SOg&6KEsJnwO;`Q>
zQ-#>3vy;Rne0GxdmB2bubC){XohUYay;19O8nnHt$rjK5xq0mnLKEtHLfkw+`cE>G
zHiavdju!OBw}-2(-(ey;L+%~_rxI{8hj$5io%q;~vVlUwPqDe-3(xdYj`-Mn_(J$(
zmIs-T=crDTc99T-rXDT<2U?B5goaGTzlPD_eGXwQoF~`FUHf>@f!u