UNPKG

cesium

Version:

Cesium is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.

1,240 lines (1,076 loc) 100 kB
/*global define*/ define([ './AttributeCompression', './barycentricCoordinates', './BoundingSphere', './Cartesian2', './Cartesian3', './Cartesian4', './Cartographic', './ComponentDatatype', './defaultValue', './defined', './DeveloperError', './EncodedCartesian3', './GeographicProjection', './Geometry', './GeometryAttribute', './GeometryInstance', './GeometryType', './IndexDatatype', './Intersect', './IntersectionTests', './Math', './Matrix3', './Matrix4', './Plane', './PrimitiveType', './Tipsify' ], function( AttributeCompression, barycentricCoordinates, BoundingSphere, Cartesian2, Cartesian3, Cartesian4, Cartographic, ComponentDatatype, defaultValue, defined, DeveloperError, EncodedCartesian3, GeographicProjection, Geometry, GeometryAttribute, GeometryInstance, GeometryType, IndexDatatype, Intersect, IntersectionTests, CesiumMath, Matrix3, Matrix4, Plane, PrimitiveType, Tipsify) { 'use strict'; /** * Content pipeline functions for geometries. * * @exports GeometryPipeline * * @see Geometry */ var GeometryPipeline = {}; function addTriangle(lines, index, i0, i1, i2) { lines[index++] = i0; lines[index++] = i1; lines[index++] = i1; lines[index++] = i2; lines[index++] = i2; lines[index] = i0; } function trianglesToLines(triangles) { var count = triangles.length; var size = (count / 3) * 6; var lines = IndexDatatype.createTypedArray(count, size); var index = 0; for ( var i = 0; i < count; i += 3, index += 6) { addTriangle(lines, index, triangles[i], triangles[i + 1], triangles[i + 2]); } return lines; } function triangleStripToLines(triangles) { var count = triangles.length; if (count >= 3) { var size = (count - 2) * 6; var lines = IndexDatatype.createTypedArray(count, size); addTriangle(lines, 0, triangles[0], triangles[1], triangles[2]); var index = 6; for ( var i = 3; i < count; ++i, index += 6) { addTriangle(lines, index, triangles[i - 1], triangles[i], triangles[i - 2]); } return lines; } return new Uint16Array(); } function triangleFanToLines(triangles) { if (triangles.length > 0) { var count = triangles.length - 1; var size = (count - 1) * 6; var lines = IndexDatatype.createTypedArray(count, size); var base = triangles[0]; var index = 0; for ( var i = 1; i < count; ++i, index += 6) { addTriangle(lines, index, base, triangles[i], triangles[i + 1]); } return lines; } return new Uint16Array(); } /** * Converts a geometry's triangle indices to line indices. If the geometry has an <code>indices</code> * and its <code>primitiveType</code> is <code>TRIANGLES</code>, <code>TRIANGLE_STRIP</code>, * <code>TRIANGLE_FAN</code>, it is converted to <code>LINES</code>; otherwise, the geometry is not changed. * <p> * This is commonly used to create a wireframe geometry for visual debugging. * </p> * * @param {Geometry} geometry The geometry to modify. * @returns {Geometry} The modified <code>geometry</code> argument, with its triangle indices converted to lines. * * @exception {DeveloperError} geometry.primitiveType must be TRIANGLES, TRIANGLE_STRIP, or TRIANGLE_FAN. * * @example * geometry = Cesium.GeometryPipeline.toWireframe(geometry); */ GeometryPipeline.toWireframe = function(geometry) { //>>includeStart('debug', pragmas.debug); if (!defined(geometry)) { throw new DeveloperError('geometry is required.'); } //>>includeEnd('debug'); var indices = geometry.indices; if (defined(indices)) { switch (geometry.primitiveType) { case PrimitiveType.TRIANGLES: geometry.indices = trianglesToLines(indices); break; case PrimitiveType.TRIANGLE_STRIP: geometry.indices = triangleStripToLines(indices); break; case PrimitiveType.TRIANGLE_FAN: geometry.indices = triangleFanToLines(indices); break; default: throw new DeveloperError('geometry.primitiveType must be TRIANGLES, TRIANGLE_STRIP, or TRIANGLE_FAN.'); } geometry.primitiveType = PrimitiveType.LINES; } return geometry; }; /** * Creates a new {@link Geometry} with <code>LINES</code> representing the provided * attribute (<code>attributeName</code>) for the provided geometry. This is used to * visualize vector attributes like normals, binormals, and tangents. * * @param {Geometry} geometry The <code>Geometry</code> instance with the attribute. * @param {String} [attributeName='normal'] The name of the attribute. * @param {Number} [length=10000.0] The length of each line segment in meters. This can be negative to point the vector in the opposite direction. * @returns {Geometry} A new <code>Geometry</code> instance with line segments for the vector. * * @exception {DeveloperError} geometry.attributes must have an attribute with the same name as the attributeName parameter. * * @example * var geometry = Cesium.GeometryPipeline.createLineSegmentsForVectors(instance.geometry, 'binormal', 100000.0); */ GeometryPipeline.createLineSegmentsForVectors = function(geometry, attributeName, length) { attributeName = defaultValue(attributeName, 'normal'); //>>includeStart('debug', pragmas.debug); if (!defined(geometry)) { throw new DeveloperError('geometry is required.'); } if (!defined(geometry.attributes.position)) { throw new DeveloperError('geometry.attributes.position is required.'); } if (!defined(geometry.attributes[attributeName])) { throw new DeveloperError('geometry.attributes must have an attribute with the same name as the attributeName parameter, ' + attributeName + '.'); } //>>includeEnd('debug'); length = defaultValue(length, 10000.0); var positions = geometry.attributes.position.values; var vectors = geometry.attributes[attributeName].values; var positionsLength = positions.length; var newPositions = new Float64Array(2 * positionsLength); var j = 0; for (var i = 0; i < positionsLength; i += 3) { newPositions[j++] = positions[i]; newPositions[j++] = positions[i + 1]; newPositions[j++] = positions[i + 2]; newPositions[j++] = positions[i] + (vectors[i] * length); newPositions[j++] = positions[i + 1] + (vectors[i + 1] * length); newPositions[j++] = positions[i + 2] + (vectors[i + 2] * length); } var newBoundingSphere; var bs = geometry.boundingSphere; if (defined(bs)) { newBoundingSphere = new BoundingSphere(bs.center, bs.radius + length); } return new Geometry({ attributes : { position : new GeometryAttribute({ componentDatatype : ComponentDatatype.DOUBLE, componentsPerAttribute : 3, values : newPositions }) }, primitiveType : PrimitiveType.LINES, boundingSphere : newBoundingSphere }); }; /** * Creates an object that maps attribute names to unique locations (indices) * for matching vertex attributes and shader programs. * * @param {Geometry} geometry The geometry, which is not modified, to create the object for. * @returns {Object} An object with attribute name / index pairs. * * @example * var attributeLocations = Cesium.GeometryPipeline.createAttributeLocations(geometry); * // Example output * // { * // 'position' : 0, * // 'normal' : 1 * // } */ GeometryPipeline.createAttributeLocations = function(geometry) { //>>includeStart('debug', pragmas.debug); if (!defined(geometry)) { throw new DeveloperError('geometry is required.'); } //>>includeEnd('debug') // There can be a WebGL performance hit when attribute 0 is disabled, so // assign attribute locations to well-known attributes. var semantics = [ 'position', 'positionHigh', 'positionLow', // From VertexFormat.position - after 2D projection and high-precision encoding 'position3DHigh', 'position3DLow', 'position2DHigh', 'position2DLow', // From Primitive 'pickColor', // From VertexFormat 'normal', 'st', 'binormal', 'tangent', // From compressing texture coordinates and normals 'compressedAttributes' ]; var attributes = geometry.attributes; var indices = {}; var j = 0; var i; var len = semantics.length; // Attribute locations for well-known attributes for (i = 0; i < len; ++i) { var semantic = semantics[i]; if (defined(attributes[semantic])) { indices[semantic] = j++; } } // Locations for custom attributes for (var name in attributes) { if (attributes.hasOwnProperty(name) && (!defined(indices[name]))) { indices[name] = j++; } } return indices; }; /** * Reorders a geometry's attributes and <code>indices</code> to achieve better performance from the GPU's pre-vertex-shader cache. * * @param {Geometry} geometry The geometry to modify. * @returns {Geometry} The modified <code>geometry</code> argument, with its attributes and indices reordered for the GPU's pre-vertex-shader cache. * * @exception {DeveloperError} Each attribute array in geometry.attributes must have the same number of attributes. * * * @example * geometry = Cesium.GeometryPipeline.reorderForPreVertexCache(geometry); * * @see GeometryPipeline.reorderForPostVertexCache */ GeometryPipeline.reorderForPreVertexCache = function(geometry) { //>>includeStart('debug', pragmas.debug); if (!defined(geometry)) { throw new DeveloperError('geometry is required.'); } //>>includeEnd('debug'); var numVertices = Geometry.computeNumberOfVertices(geometry); var indices = geometry.indices; if (defined(indices)) { var indexCrossReferenceOldToNew = new Int32Array(numVertices); for ( var i = 0; i < numVertices; i++) { indexCrossReferenceOldToNew[i] = -1; } // Construct cross reference and reorder indices var indicesIn = indices; var numIndices = indicesIn.length; var indicesOut = IndexDatatype.createTypedArray(numVertices, numIndices); var intoIndicesIn = 0; var intoIndicesOut = 0; var nextIndex = 0; var tempIndex; while (intoIndicesIn < numIndices) { tempIndex = indexCrossReferenceOldToNew[indicesIn[intoIndicesIn]]; if (tempIndex !== -1) { indicesOut[intoIndicesOut] = tempIndex; } else { tempIndex = indicesIn[intoIndicesIn]; indexCrossReferenceOldToNew[tempIndex] = nextIndex; indicesOut[intoIndicesOut] = nextIndex; ++nextIndex; } ++intoIndicesIn; ++intoIndicesOut; } geometry.indices = indicesOut; // Reorder attributes var attributes = geometry.attributes; for ( var property in attributes) { if (attributes.hasOwnProperty(property) && defined(attributes[property]) && defined(attributes[property].values)) { var attribute = attributes[property]; var elementsIn = attribute.values; var intoElementsIn = 0; var numComponents = attribute.componentsPerAttribute; var elementsOut = ComponentDatatype.createTypedArray(attribute.componentDatatype, nextIndex * numComponents); while (intoElementsIn < numVertices) { var temp = indexCrossReferenceOldToNew[intoElementsIn]; if (temp !== -1) { for (i = 0; i < numComponents; i++) { elementsOut[numComponents * temp + i] = elementsIn[numComponents * intoElementsIn + i]; } } ++intoElementsIn; } attribute.values = elementsOut; } } } return geometry; }; /** * Reorders a geometry's <code>indices</code> to achieve better performance from the GPU's * post vertex-shader cache by using the Tipsify algorithm. If the geometry <code>primitiveType</code> * is not <code>TRIANGLES</code> or the geometry does not have an <code>indices</code>, this function has no effect. * * @param {Geometry} geometry The geometry to modify. * @param {Number} [cacheCapacity=24] The number of vertices that can be held in the GPU's vertex cache. * @returns {Geometry} The modified <code>geometry</code> argument, with its indices reordered for the post-vertex-shader cache. * * @exception {DeveloperError} cacheCapacity must be greater than two. * * * @example * geometry = Cesium.GeometryPipeline.reorderForPostVertexCache(geometry); * * @see GeometryPipeline.reorderForPreVertexCache * @see {@link http://gfx.cs.princ0eton.edu/pubs/Sander_2007_%3ETR/tipsy.pdf|Fast Triangle Reordering for Vertex Locality and Reduced Overdraw} * by Sander, Nehab, and Barczak */ GeometryPipeline.reorderForPostVertexCache = function(geometry, cacheCapacity) { //>>includeStart('debug', pragmas.debug); if (!defined(geometry)) { throw new DeveloperError('geometry is required.'); } //>>includeEnd('debug'); var indices = geometry.indices; if ((geometry.primitiveType === PrimitiveType.TRIANGLES) && (defined(indices))) { var numIndices = indices.length; var maximumIndex = 0; for ( var j = 0; j < numIndices; j++) { if (indices[j] > maximumIndex) { maximumIndex = indices[j]; } } geometry.indices = Tipsify.tipsify({ indices : indices, maximumIndex : maximumIndex, cacheSize : cacheCapacity }); } return geometry; }; function copyAttributesDescriptions(attributes) { var newAttributes = {}; for ( var attribute in attributes) { if (attributes.hasOwnProperty(attribute) && defined(attributes[attribute]) && defined(attributes[attribute].values)) { var attr = attributes[attribute]; newAttributes[attribute] = new GeometryAttribute({ componentDatatype : attr.componentDatatype, componentsPerAttribute : attr.componentsPerAttribute, normalize : attr.normalize, values : [] }); } } return newAttributes; } function copyVertex(destinationAttributes, sourceAttributes, index) { for ( var attribute in sourceAttributes) { if (sourceAttributes.hasOwnProperty(attribute) && defined(sourceAttributes[attribute]) && defined(sourceAttributes[attribute].values)) { var attr = sourceAttributes[attribute]; for ( var k = 0; k < attr.componentsPerAttribute; ++k) { destinationAttributes[attribute].values.push(attr.values[(index * attr.componentsPerAttribute) + k]); } } } } /** * Splits a geometry into multiple geometries, if necessary, to ensure that indices in the * <code>indices</code> fit into unsigned shorts. This is used to meet the WebGL requirements * when unsigned int indices are not supported. * <p> * If the geometry does not have any <code>indices</code>, this function has no effect. * </p> * * @param {Geometry} geometry The geometry to be split into multiple geometries. * @returns {Geometry[]} An array of geometries, each with indices that fit into unsigned shorts. * * @exception {DeveloperError} geometry.primitiveType must equal to PrimitiveType.TRIANGLES, PrimitiveType.LINES, or PrimitiveType.POINTS * @exception {DeveloperError} All geometry attribute lists must have the same number of attributes. * * @example * var geometries = Cesium.GeometryPipeline.fitToUnsignedShortIndices(geometry); */ GeometryPipeline.fitToUnsignedShortIndices = function(geometry) { //>>includeStart('debug', pragmas.debug); if (!defined(geometry)) { throw new DeveloperError('geometry is required.'); } if ((defined(geometry.indices)) && ((geometry.primitiveType !== PrimitiveType.TRIANGLES) && (geometry.primitiveType !== PrimitiveType.LINES) && (geometry.primitiveType !== PrimitiveType.POINTS))) { throw new DeveloperError('geometry.primitiveType must equal to PrimitiveType.TRIANGLES, PrimitiveType.LINES, or PrimitiveType.POINTS.'); } //>>includeEnd('debug'); var geometries = []; // If there's an index list and more than 64K attributes, it is possible that // some indices are outside the range of unsigned short [0, 64K - 1] var numberOfVertices = Geometry.computeNumberOfVertices(geometry); if (defined(geometry.indices) && (numberOfVertices >= CesiumMath.SIXTY_FOUR_KILOBYTES)) { var oldToNewIndex = []; var newIndices = []; var currentIndex = 0; var newAttributes = copyAttributesDescriptions(geometry.attributes); var originalIndices = geometry.indices; var numberOfIndices = originalIndices.length; var indicesPerPrimitive; if (geometry.primitiveType === PrimitiveType.TRIANGLES) { indicesPerPrimitive = 3; } else if (geometry.primitiveType === PrimitiveType.LINES) { indicesPerPrimitive = 2; } else if (geometry.primitiveType === PrimitiveType.POINTS) { indicesPerPrimitive = 1; } for ( var j = 0; j < numberOfIndices; j += indicesPerPrimitive) { for (var k = 0; k < indicesPerPrimitive; ++k) { var x = originalIndices[j + k]; var i = oldToNewIndex[x]; if (!defined(i)) { i = currentIndex++; oldToNewIndex[x] = i; copyVertex(newAttributes, geometry.attributes, x); } newIndices.push(i); } if (currentIndex + indicesPerPrimitive >= CesiumMath.SIXTY_FOUR_KILOBYTES) { geometries.push(new Geometry({ attributes : newAttributes, indices : newIndices, primitiveType : geometry.primitiveType, boundingSphere : geometry.boundingSphere, boundingSphereCV : geometry.boundingSphereCV })); // Reset for next vertex-array oldToNewIndex = []; newIndices = []; currentIndex = 0; newAttributes = copyAttributesDescriptions(geometry.attributes); } } if (newIndices.length !== 0) { geometries.push(new Geometry({ attributes : newAttributes, indices : newIndices, primitiveType : geometry.primitiveType, boundingSphere : geometry.boundingSphere, boundingSphereCV : geometry.boundingSphereCV })); } } else { // No need to split into multiple geometries geometries.push(geometry); } return geometries; }; var scratchProjectTo2DCartesian3 = new Cartesian3(); var scratchProjectTo2DCartographic = new Cartographic(); /** * Projects a geometry's 3D <code>position</code> attribute to 2D, replacing the <code>position</code> * attribute with separate <code>position3D</code> and <code>position2D</code> attributes. * <p> * If the geometry does not have a <code>position</code>, this function has no effect. * </p> * * @param {Geometry} geometry The geometry to modify. * @param {String} attributeName The name of the attribute. * @param {String} attributeName3D The name of the attribute in 3D. * @param {String} attributeName2D The name of the attribute in 2D. * @param {Object} [projection=new GeographicProjection()] The projection to use. * @returns {Geometry} The modified <code>geometry</code> argument with <code>position3D</code> and <code>position2D</code> attributes. * * @exception {DeveloperError} geometry must have attribute matching the attributeName argument. * @exception {DeveloperError} The attribute componentDatatype must be ComponentDatatype.DOUBLE. * @exception {DeveloperError} Could not project a point to 2D. * * @example * geometry = Cesium.GeometryPipeline.projectTo2D(geometry, 'position', 'position3D', 'position2D'); */ GeometryPipeline.projectTo2D = function(geometry, attributeName, attributeName3D, attributeName2D, projection) { //>>includeStart('debug', pragmas.debug); if (!defined(geometry)) { throw new DeveloperError('geometry is required.'); } if (!defined(attributeName)) { throw new DeveloperError('attributeName is required.'); } if (!defined(attributeName3D)) { throw new DeveloperError('attributeName3D is required.'); } if (!defined(attributeName2D)) { throw new DeveloperError('attributeName2D is required.'); } if (!defined(geometry.attributes[attributeName])) { throw new DeveloperError('geometry must have attribute matching the attributeName argument: ' + attributeName + '.'); } if (geometry.attributes[attributeName].componentDatatype !== ComponentDatatype.DOUBLE) { throw new DeveloperError('The attribute componentDatatype must be ComponentDatatype.DOUBLE.'); } //>>includeEnd('debug'); var attribute = geometry.attributes[attributeName]; projection = (defined(projection)) ? projection : new GeographicProjection(); var ellipsoid = projection.ellipsoid; // Project original values to 2D. var values3D = attribute.values; var projectedValues = new Float64Array(values3D.length); var index = 0; for ( var i = 0; i < values3D.length; i += 3) { var value = Cartesian3.fromArray(values3D, i, scratchProjectTo2DCartesian3); var lonLat = ellipsoid.cartesianToCartographic(value, scratchProjectTo2DCartographic); if (!defined(lonLat)) { throw new DeveloperError('Could not project point (' + value.x + ', ' + value.y + ', ' + value.z + ') to 2D.'); } var projectedLonLat = projection.project(lonLat, scratchProjectTo2DCartesian3); projectedValues[index++] = projectedLonLat.x; projectedValues[index++] = projectedLonLat.y; projectedValues[index++] = projectedLonLat.z; } // Rename original cartesians to WGS84 cartesians. geometry.attributes[attributeName3D] = attribute; // Replace original cartesians with 2D projected cartesians geometry.attributes[attributeName2D] = new GeometryAttribute({ componentDatatype : ComponentDatatype.DOUBLE, componentsPerAttribute : 3, values : projectedValues }); delete geometry.attributes[attributeName]; return geometry; }; var encodedResult = { high : 0.0, low : 0.0 }; /** * Encodes floating-point geometry attribute values as two separate attributes to improve * rendering precision. * <p> * This is commonly used to create high-precision position vertex attributes. * </p> * * @param {Geometry} geometry The geometry to modify. * @param {String} attributeName The name of the attribute. * @param {String} attributeHighName The name of the attribute for the encoded high bits. * @param {String} attributeLowName The name of the attribute for the encoded low bits. * @returns {Geometry} The modified <code>geometry</code> argument, with its encoded attribute. * * @exception {DeveloperError} geometry must have attribute matching the attributeName argument. * @exception {DeveloperError} The attribute componentDatatype must be ComponentDatatype.DOUBLE. * * @example * geometry = Cesium.GeometryPipeline.encodeAttribute(geometry, 'position3D', 'position3DHigh', 'position3DLow'); */ GeometryPipeline.encodeAttribute = function(geometry, attributeName, attributeHighName, attributeLowName) { //>>includeStart('debug', pragmas.debug); if (!defined(geometry)) { throw new DeveloperError('geometry is required.'); } if (!defined(attributeName)) { throw new DeveloperError('attributeName is required.'); } if (!defined(attributeHighName)) { throw new DeveloperError('attributeHighName is required.'); } if (!defined(attributeLowName)) { throw new DeveloperError('attributeLowName is required.'); } if (!defined(geometry.attributes[attributeName])) { throw new DeveloperError('geometry must have attribute matching the attributeName argument: ' + attributeName + '.'); } if (geometry.attributes[attributeName].componentDatatype !== ComponentDatatype.DOUBLE) { throw new DeveloperError('The attribute componentDatatype must be ComponentDatatype.DOUBLE.'); } //>>includeEnd('debug'); var attribute = geometry.attributes[attributeName]; var values = attribute.values; var length = values.length; var highValues = new Float32Array(length); var lowValues = new Float32Array(length); for (var i = 0; i < length; ++i) { EncodedCartesian3.encode(values[i], encodedResult); highValues[i] = encodedResult.high; lowValues[i] = encodedResult.low; } var componentsPerAttribute = attribute.componentsPerAttribute; geometry.attributes[attributeHighName] = new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : componentsPerAttribute, values : highValues }); geometry.attributes[attributeLowName] = new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : componentsPerAttribute, values : lowValues }); delete geometry.attributes[attributeName]; return geometry; }; var scratchCartesian3 = new Cartesian3(); function transformPoint(matrix, attribute) { if (defined(attribute)) { var values = attribute.values; var length = values.length; for (var i = 0; i < length; i += 3) { Cartesian3.unpack(values, i, scratchCartesian3); Matrix4.multiplyByPoint(matrix, scratchCartesian3, scratchCartesian3); Cartesian3.pack(scratchCartesian3, values, i); } } } function transformVector(matrix, attribute) { if (defined(attribute)) { var values = attribute.values; var length = values.length; for (var i = 0; i < length; i += 3) { Cartesian3.unpack(values, i, scratchCartesian3); Matrix3.multiplyByVector(matrix, scratchCartesian3, scratchCartesian3); scratchCartesian3 = Cartesian3.normalize(scratchCartesian3, scratchCartesian3); Cartesian3.pack(scratchCartesian3, values, i); } } } var inverseTranspose = new Matrix4(); var normalMatrix = new Matrix3(); /** * Transforms a geometry instance to world coordinates. This changes * the instance's <code>modelMatrix</code> to {@link Matrix4.IDENTITY} and transforms the * following attributes if they are present: <code>position</code>, <code>normal</code>, * <code>binormal</code>, and <code>tangent</code>. * * @param {GeometryInstance} instance The geometry instance to modify. * @returns {GeometryInstance} The modified <code>instance</code> argument, with its attributes transforms to world coordinates. * * @example * Cesium.GeometryPipeline.transformToWorldCoordinates(instance); */ GeometryPipeline.transformToWorldCoordinates = function(instance) { //>>includeStart('debug', pragmas.debug); if (!defined(instance)) { throw new DeveloperError('instance is required.'); } //>>includeEnd('debug'); var modelMatrix = instance.modelMatrix; if (Matrix4.equals(modelMatrix, Matrix4.IDENTITY)) { // Already in world coordinates return instance; } var attributes = instance.geometry.attributes; // Transform attributes in known vertex formats transformPoint(modelMatrix, attributes.position); transformPoint(modelMatrix, attributes.prevPosition); transformPoint(modelMatrix, attributes.nextPosition); if ((defined(attributes.normal)) || (defined(attributes.binormal)) || (defined(attributes.tangent))) { Matrix4.inverse(modelMatrix, inverseTranspose); Matrix4.transpose(inverseTranspose, inverseTranspose); Matrix4.getRotation(inverseTranspose, normalMatrix); transformVector(normalMatrix, attributes.normal); transformVector(normalMatrix, attributes.binormal); transformVector(normalMatrix, attributes.tangent); } var boundingSphere = instance.geometry.boundingSphere; if (defined(boundingSphere)) { instance.geometry.boundingSphere = BoundingSphere.transform(boundingSphere, modelMatrix, boundingSphere); } instance.modelMatrix = Matrix4.clone(Matrix4.IDENTITY); return instance; }; function findAttributesInAllGeometries(instances, propertyName) { var length = instances.length; var attributesInAllGeometries = {}; var attributes0 = instances[0][propertyName].attributes; var name; for (name in attributes0) { if (attributes0.hasOwnProperty(name) && defined(attributes0[name]) && defined(attributes0[name].values)) { var attribute = attributes0[name]; var numberOfComponents = attribute.values.length; var inAllGeometries = true; // Does this same attribute exist in all geometries? for (var i = 1; i < length; ++i) { var otherAttribute = instances[i][propertyName].attributes[name]; if ((!defined(otherAttribute)) || (attribute.componentDatatype !== otherAttribute.componentDatatype) || (attribute.componentsPerAttribute !== otherAttribute.componentsPerAttribute) || (attribute.normalize !== otherAttribute.normalize)) { inAllGeometries = false; break; } numberOfComponents += otherAttribute.values.length; } if (inAllGeometries) { attributesInAllGeometries[name] = new GeometryAttribute({ componentDatatype : attribute.componentDatatype, componentsPerAttribute : attribute.componentsPerAttribute, normalize : attribute.normalize, values : ComponentDatatype.createTypedArray(attribute.componentDatatype, numberOfComponents) }); } } } return attributesInAllGeometries; } var tempScratch = new Cartesian3(); function combineGeometries(instances, propertyName) { var length = instances.length; var name; var i; var j; var k; var m = instances[0].modelMatrix; var haveIndices = (defined(instances[0][propertyName].indices)); var primitiveType = instances[0][propertyName].primitiveType; //>>includeStart('debug', pragmas.debug); for (i = 1; i < length; ++i) { if (!Matrix4.equals(instances[i].modelMatrix, m)) { throw new DeveloperError('All instances must have the same modelMatrix.'); } if ((defined(instances[i][propertyName].indices)) !== haveIndices) { throw new DeveloperError('All instance geometries must have an indices or not have one.'); } if (instances[i][propertyName].primitiveType !== primitiveType) { throw new DeveloperError('All instance geometries must have the same primitiveType.'); } } //>>includeEnd('debug'); // Find subset of attributes in all geometries var attributes = findAttributesInAllGeometries(instances, propertyName); var values; var sourceValues; var sourceValuesLength; // Combine attributes from each geometry into a single typed array for (name in attributes) { if (attributes.hasOwnProperty(name)) { values = attributes[name].values; k = 0; for (i = 0; i < length; ++i) { sourceValues = instances[i][propertyName].attributes[name].values; sourceValuesLength = sourceValues.length; for (j = 0; j < sourceValuesLength; ++j) { values[k++] = sourceValues[j]; } } } } // Combine index lists var indices; if (haveIndices) { var numberOfIndices = 0; for (i = 0; i < length; ++i) { numberOfIndices += instances[i][propertyName].indices.length; } var numberOfVertices = Geometry.computeNumberOfVertices(new Geometry({ attributes : attributes, primitiveType : PrimitiveType.POINTS })); var destIndices = IndexDatatype.createTypedArray(numberOfVertices, numberOfIndices); var destOffset = 0; var offset = 0; for (i = 0; i < length; ++i) { var sourceIndices = instances[i][propertyName].indices; var sourceIndicesLen = sourceIndices.length; for (k = 0; k < sourceIndicesLen; ++k) { destIndices[destOffset++] = offset + sourceIndices[k]; } offset += Geometry.computeNumberOfVertices(instances[i][propertyName]); } indices = destIndices; } // Create bounding sphere that includes all instances var center = new Cartesian3(); var radius = 0.0; var bs; for (i = 0; i < length; ++i) { bs = instances[i][propertyName].boundingSphere; if (!defined(bs)) { // If any geometries have an undefined bounding sphere, then so does the combined geometry center = undefined; break; } Cartesian3.add(bs.center, center, center); } if (defined(center)) { Cartesian3.divideByScalar(center, length, center); for (i = 0; i < length; ++i) { bs = instances[i][propertyName].boundingSphere; var tempRadius = Cartesian3.magnitude(Cartesian3.subtract(bs.center, center, tempScratch)) + bs.radius; if (tempRadius > radius) { radius = tempRadius; } } } return new Geometry({ attributes : attributes, indices : indices, primitiveType : primitiveType, boundingSphere : (defined(center)) ? new BoundingSphere(center, radius) : undefined }); } /** * Combines geometry from several {@link GeometryInstance} objects into one geometry. * This concatenates the attributes, concatenates and adjusts the indices, and creates * a bounding sphere encompassing all instances. * <p> * If the instances do not have the same attributes, a subset of attributes common * to all instances is used, and the others are ignored. * </p> * <p> * This is used by {@link Primitive} to efficiently render a large amount of static data. * </p> * * @private * * @param {GeometryInstance[]} [instances] The array of {@link GeometryInstance} objects whose geometry will be combined. * @returns {Geometry} A single geometry created from the provided geometry instances. * * @exception {DeveloperError} All instances must have the same modelMatrix. * @exception {DeveloperError} All instance geometries must have an indices or not have one. * @exception {DeveloperError} All instance geometries must have the same primitiveType. * * * @example * for (var i = 0; i < instances.length; ++i) { * Cesium.GeometryPipeline.transformToWorldCoordinates(instances[i]); * } * var geometries = Cesium.GeometryPipeline.combineInstances(instances); * * @see GeometryPipeline.transformToWorldCoordinates */ GeometryPipeline.combineInstances = function(instances) { //>>includeStart('debug', pragmas.debug); if ((!defined(instances)) || (instances.length < 1)) { throw new DeveloperError('instances is required and must have length greater than zero.'); } //>>includeEnd('debug'); var instanceGeometry = []; var instanceSplitGeometry = []; var length = instances.length; for (var i = 0; i < length; ++i) { var instance = instances[i]; if (defined(instance.geometry)) { instanceGeometry.push(instance); } else { instanceSplitGeometry.push(instance); } } var geometries = []; if (instanceGeometry.length > 0) { geometries.push(combineGeometries(instanceGeometry, 'geometry')); } if (instanceSplitGeometry.length > 0) { geometries.push(combineGeometries(instanceSplitGeometry, 'westHemisphereGeometry')); geometries.push(combineGeometries(instanceSplitGeometry, 'eastHemisphereGeometry')); } return geometries; }; var normal = new Cartesian3(); var v0 = new Cartesian3(); var v1 = new Cartesian3(); var v2 = new Cartesian3(); /** * Computes per-vertex normals for a geometry containing <code>TRIANGLES</code> by averaging the normals of * all triangles incident to the vertex. The result is a new <code>normal</code> attribute added to the geometry. * This assumes a counter-clockwise winding order. * * @param {Geometry} geometry The geometry to modify. * @returns {Geometry} The modified <code>geometry</code> argument with the computed <code>normal</code> attribute. * * @exception {DeveloperError} geometry.indices length must be greater than 0 and be a multiple of 3. * @exception {DeveloperError} geometry.primitiveType must be {@link PrimitiveType.TRIANGLES}. * * @example * Cesium.GeometryPipeline.computeNormal(geometry); */ GeometryPipeline.computeNormal = function(geometry) { //>>includeStart('debug', pragmas.debug); if (!defined(geometry)) { throw new DeveloperError('geometry is required.'); } if (!defined(geometry.attributes.position) || !defined(geometry.attributes.position.values)) { throw new DeveloperError('geometry.attributes.position.values is required.'); } if (!defined(geometry.indices)) { throw new DeveloperError('geometry.indices is required.'); } if (geometry.indices.length < 2 || geometry.indices.length % 3 !== 0) { throw new DeveloperError('geometry.indices length must be greater than 0 and be a multiple of 3.'); } if (geometry.primitiveType !== PrimitiveType.TRIANGLES) { throw new DeveloperError('geometry.primitiveType must be PrimitiveType.TRIANGLES.'); } //>>includeEnd('debug'); var indices = geometry.indices; var attributes = geometry.attributes; var vertices = attributes.position.values; var numVertices = attributes.position.values.length / 3; var numIndices = indices.length; var normalsPerVertex = new Array(numVertices); var normalsPerTriangle = new Array(numIndices / 3); var normalIndices = new Array(numIndices); for ( var i = 0; i < numVertices; i++) { normalsPerVertex[i] = { indexOffset : 0, count : 0, currentCount : 0 }; } var j = 0; for (i = 0; i < numIndices; i += 3) { var i0 = indices[i]; var i1 = indices[i + 1]; var i2 = indices[i + 2]; var i03 = i0 * 3; var i13 = i1 * 3; var i23 = i2 * 3; v0.x = vertices[i03]; v0.y = vertices[i03 + 1]; v0.z = vertices[i03 + 2]; v1.x = vertices[i13]; v1.y = vertices[i13 + 1]; v1.z = vertices[i13 + 2]; v2.x = vertices[i23]; v2.y = vertices[i23 + 1]; v2.z = vertices[i23 + 2]; normalsPerVertex[i0].count++; normalsPerVertex[i1].count++; normalsPerVertex[i2].count++; Cartesian3.subtract(v1, v0, v1); Cartesian3.subtract(v2, v0, v2); normalsPerTriangle[j] = Cartesian3.cross(v1, v2, new Cartesian3()); j++; } var indexOffset = 0; for (i = 0; i < numVertices; i++) { normalsPerVertex[i].indexOffset += indexOffset; indexOffset += normalsPerVertex[i].count; } j = 0; var vertexNormalData; for (i = 0; i < numIndices; i += 3) { vertexNormalData = normalsPerVertex[indices[i]]; var index = vertexNormalData.indexOffset + vertexNormalData.currentCount; normalIndices[index] = j; vertexNormalData.currentCount++; vertexNormalData = normalsPerVertex[indices[i + 1]]; index = vertexNormalData.indexOffset + vertexNormalData.currentCount; normalIndices[index] = j; vertexNormalData.currentCount++; vertexNormalData = normalsPerVertex[indices[i + 2]]; index = vertexNormalData.indexOffset + vertexNormalData.currentCount; normalIndices[index] = j; vertexNormalData.currentCount++; j++; } var normalValues = new Float32Array(numVertices * 3); for (i = 0; i < numVertices; i++) { var i3 = i * 3; vertexNormalData = normalsPerVertex[i]; if (vertexNormalData.count > 0) { Cartesian3.clone(Cartesian3.ZERO, normal); for (j = 0; j < vertexNormalData.count; j++) { Cartesian3.add(normal, normalsPerTriangle[normalIndices[vertexNormalData.indexOffset + j]], normal); } Cartesian3.normalize(normal, normal); normalValues[i3] = normal.x; normalValues[i3 + 1] = normal.y; normalValues[i3 + 2] = normal.z; } else { normalValues[i3] = 0.0; normalValues[i3 + 1] = 0.0; normalValues[i3 + 2] = 1.0; } } geometry.attributes.normal = new GeometryAttribute({ componentDatatype : ComponentDatatype.FLOAT, componentsPerAttribute : 3, values : normalValues }); return geometry; }; var normalScratch = new Cartesian3(); var normalScale = new Cartesian3(); var tScratch = new Cartesian3(); /** * Computes per-vertex binormals and tangents for a geometry containing <code>TRIANGLES</code>. * The result is new <code>binormal</code> and <code>tangent</code> attributes added to the geometry. * This assumes a counter-clockwise winding order. * <p> * Based on <a href="http://www.terathon.com/code/tangent.html">Computing Tangent Space Basis Vectors * for an Arbitrary Mesh</a> by Eric Lengyel. * </p> * * @param {Geometry} geometry The geometry to modify. * @returns {Geometry} The modified <code>geometry</code> argument with the computed <code>binormal</code> and <code>tangent</code> attributes. * * @exception {DeveloperError} geometry.indices length must be greater than 0 and be a multiple of 3. * @exception {DeveloperError} geometry.primitiveType must be {@link PrimitiveType.TRIANGLES}. * * @example * Cesium.GeometryPipeline.computeBinormalAndTangent(geometry); */ GeometryPipeline.computeBinormalAndTangent = function(geometry) { //>>includeStart('debug', pragmas.debug); if (!defined(geometry)) { throw new DeveloperError('geometry is required.'); } //>>includeEnd('debug'); var attributes = geometry.attributes; var indices = geometry.indices; //>>includeStart('debug', pragmas.debug); if (!defined(attributes.position) || !defined(attributes.position.values)) { throw new DeveloperError('geometry.attributes.position.values is required.'); } if (!defined(attributes.normal) || !defined(attributes.normal.values)) { throw new DeveloperError('geometry.attributes.normal.values is required.'); } if (!defined(attributes.st) || !defined(attributes.st.values)) { throw new DeveloperError('geometry.attributes.st.values is required.'); } if (!defined(indices)) { throw new DeveloperError('geometry.indices is required.'); } if (indices.length < 2 || indices.length % 3 !== 0) { throw new DeveloperError('geometry.ind