/*global define*/ define([ '../Core/AttributeCompression', '../Core/BoundingSphere', '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartographic', '../Core/defined', '../Core/Ellipsoid', '../Core/EllipsoidalOccluder', '../Core/IndexDatatype', '../Core/Intersections2D', '../Core/Math', './createTaskProcessorWorker' ], function( AttributeCompression, BoundingSphere, Cartesian2, Cartesian3, Cartographic, defined, Ellipsoid, EllipsoidalOccluder, IndexDatatype, Intersections2D, CesiumMath, createTaskProcessorWorker) { "use strict"; var maxShort = 32767; var halfMaxShort = (maxShort / 2) | 0; var clipScratch = []; var clipScratch2 = []; var verticesScratch = []; var cartographicScratch = new Cartographic(); var cartesian3Scratch = new Cartesian3(); var uScratch = []; var vScratch = []; var heightScratch = []; var indicesScratch = []; var normalsScratch = []; var horizonOcclusionPointScratch = new Cartesian3(); var boundingSphereScratch = new BoundingSphere(); function upsampleQuantizedTerrainMesh(parameters, transferableObjects) { var isEastChild = parameters.isEastChild; var isNorthChild = parameters.isNorthChild; var minU = isEastChild ? halfMaxShort : 0; var maxU = isEastChild ? maxShort : halfMaxShort; var minV = isNorthChild ? halfMaxShort : 0; var maxV = isNorthChild ? maxShort : halfMaxShort; var uBuffer = uScratch; var vBuffer = vScratch; var heightBuffer = heightScratch; var normalBuffer = normalsScratch; uBuffer.length = 0; vBuffer.length = 0; heightBuffer.length = 0; normalBuffer.length = 0; var indices = indicesScratch; indices.length = 0; var vertexMap = {}; var parentVertices = parameters.vertices; var parentNormalBuffer = parameters.encodedNormals; var parentIndices = parameters.indices; var quantizedVertexCount = parentVertices.length / 3; var parentUBuffer = parentVertices.subarray(0, quantizedVertexCount); var parentVBuffer = parentVertices.subarray(quantizedVertexCount, 2 * quantizedVertexCount); var parentHeightBuffer = parentVertices.subarray(quantizedVertexCount * 2, 3 * quantizedVertexCount); var vertexCount = 0; var hasVertexNormals = defined(parentNormalBuffer); var i, n, u, v; for (i = 0, n = 0; i < quantizedVertexCount; ++i, n += 2) { u = parentUBuffer[i]; v = parentVBuffer[i]; if ((isEastChild && u >= halfMaxShort || !isEastChild && u <= halfMaxShort) && (isNorthChild && v >= halfMaxShort || !isNorthChild && v <= halfMaxShort)) { vertexMap[i] = vertexCount; uBuffer.push(u); vBuffer.push(v); heightBuffer.push(parentHeightBuffer[i]); if (hasVertexNormals) { normalBuffer.push(parentNormalBuffer[n]); normalBuffer.push(parentNormalBuffer[n + 1]); } ++vertexCount; } } var triangleVertices = []; triangleVertices.push(new Vertex()); triangleVertices.push(new Vertex()); triangleVertices.push(new Vertex()); var clippedTriangleVertices = []; clippedTriangleVertices.push(new Vertex()); clippedTriangleVertices.push(new Vertex()); clippedTriangleVertices.push(new Vertex()); var clippedIndex; var clipped2; for (i = 0; i < parentIndices.length; i += 3) { var i0 = parentIndices[i]; var i1 = parentIndices[i + 1]; var i2 = parentIndices[i + 2]; var u0 = parentUBuffer[i0]; var u1 = parentUBuffer[i1]; var u2 = parentUBuffer[i2]; triangleVertices[0].initializeIndexed(parentUBuffer, parentVBuffer, parentHeightBuffer, parentNormalBuffer, i0); triangleVertices[1].initializeIndexed(parentUBuffer, parentVBuffer, parentHeightBuffer, parentNormalBuffer, i1); triangleVertices[2].initializeIndexed(parentUBuffer, parentVBuffer, parentHeightBuffer, parentNormalBuffer, i2); // Clip triangle on the east-west boundary. var clipped = Intersections2D.clipTriangleAtAxisAlignedThreshold(halfMaxShort, isEastChild, u0, u1, u2, clipScratch); // Get the first clipped triangle, if any. clippedIndex = 0; if (clippedIndex >= clipped.length) { continue; } clippedIndex = clippedTriangleVertices[0].initializeFromClipResult(clipped, clippedIndex, triangleVertices); if (clippedIndex >= clipped.length) { continue; } clippedIndex = clippedTriangleVertices[1].initializeFromClipResult(clipped, clippedIndex, triangleVertices); if (clippedIndex >= clipped.length) { continue; } clippedIndex = clippedTriangleVertices[2].initializeFromClipResult(clipped, clippedIndex, triangleVertices); // Clip the triangle against the North-south boundary. clipped2 = Intersections2D.clipTriangleAtAxisAlignedThreshold(halfMaxShort, isNorthChild, clippedTriangleVertices[0].getV(), clippedTriangleVertices[1].getV(), clippedTriangleVertices[2].getV(), clipScratch2); addClippedPolygon(uBuffer, vBuffer, heightBuffer, normalBuffer, indices, vertexMap, clipped2, clippedTriangleVertices, hasVertexNormals); // If there's another vertex in the original clipped result, // it forms a second triangle. Clip it as well. if (clippedIndex < clipped.length) { clippedTriangleVertices[2].clone(clippedTriangleVertices[1]); clippedTriangleVertices[2].initializeFromClipResult(clipped, clippedIndex, triangleVertices); clipped2 = Intersections2D.clipTriangleAtAxisAlignedThreshold(halfMaxShort, isNorthChild, clippedTriangleVertices[0].getV(), clippedTriangleVertices[1].getV(), clippedTriangleVertices[2].getV(), clipScratch2); addClippedPolygon(uBuffer, vBuffer, heightBuffer, normalBuffer, indices, vertexMap, clipped2, clippedTriangleVertices, hasVertexNormals); } } var uOffset = isEastChild ? -maxShort : 0; var vOffset = isNorthChild ? -maxShort : 0; var parentMinimumHeight = parameters.minimumHeight; var parentMaximumHeight = parameters.maximumHeight; var westIndices = []; var southIndices = []; var eastIndices = []; var northIndices = []; var minimumHeight = Number.MAX_VALUE; var maximumHeight = -minimumHeight; var cartesianVertices = verticesScratch; cartesianVertices.length = 0; var ellipsoid = Ellipsoid.clone(parameters.ellipsoid); var rectangle = parameters.childRectangle; var north = rectangle.north; var south = rectangle.south; var east = rectangle.east; var west = rectangle.west; if (east < west) { east += CesiumMath.TWO_PI; } for (i = 0; i < uBuffer.length; ++i) { u = Math.round(uBuffer[i]); if (u <= minU) { westIndices.push(i); u = 0; } else if (u >= maxU) { eastIndices.push(i); u = maxShort; } else { u = u * 2 + uOffset; } uBuffer[i] = u; v = Math.round(vBuffer[i]); if (v <= minV) { southIndices.push(i); v = 0; } else if (v >= maxV) { northIndices.push(i); v = maxShort; } else { v = v * 2 + vOffset; } vBuffer[i] = v; var height = CesiumMath.lerp(parentMinimumHeight, parentMaximumHeight, heightBuffer[i] / maxShort); if (height < minimumHeight) { minimumHeight = height; } if (height > maximumHeight) { maximumHeight = height; } heightBuffer[i] = height; cartographicScratch.longitude = CesiumMath.lerp(west, east, u / maxShort); cartographicScratch.latitude = CesiumMath.lerp(south, north, v / maxShort); cartographicScratch.height = height; ellipsoid.cartographicToCartesian(cartographicScratch, cartesian3Scratch); cartesianVertices.push(cartesian3Scratch.x); cartesianVertices.push(cartesian3Scratch.y); cartesianVertices.push(cartesian3Scratch.z); } var boundingSphere = BoundingSphere.fromVertices(cartesianVertices, Cartesian3.ZERO, 3, boundingSphereScratch); var occluder = new EllipsoidalOccluder(ellipsoid); var horizonOcclusionPoint = occluder.computeHorizonCullingPointFromVertices(boundingSphere.center, cartesianVertices, 3, boundingSphere.center, horizonOcclusionPointScratch); var heightRange = maximumHeight - minimumHeight; var vertices = new Uint16Array(uBuffer.length + vBuffer.length + heightBuffer.length); for (i = 0; i < uBuffer.length; ++i) { vertices[i] = uBuffer[i]; } var start = uBuffer.length; for (i = 0; i < vBuffer.length; ++i) { vertices[start + i] = vBuffer[i]; } start += vBuffer.length; for (i = 0; i < heightBuffer.length; ++i) { vertices[start + i] = maxShort * (heightBuffer[i] - minimumHeight) / heightRange; } var indicesTypedArray = IndexDatatype.createTypedArray(uBuffer.length, indices); var encodedNormals; if (hasVertexNormals) { var normalArray = new Uint8Array(normalBuffer); transferableObjects.push(vertices.buffer, indicesTypedArray.buffer, normalArray.buffer); encodedNormals = normalArray.buffer; } else { transferableObjects.push(vertices.buffer, indicesTypedArray.buffer); } return { vertices : vertices.buffer, encodedNormals : encodedNormals, indices : indicesTypedArray.buffer, minimumHeight : minimumHeight, maximumHeight : maximumHeight, westIndices : westIndices, southIndices : southIndices, eastIndices : eastIndices, northIndices : northIndices, boundingSphere : boundingSphere, horizonOcclusionPoint : horizonOcclusionPoint }; } function Vertex() { this.vertexBuffer = undefined; this.index = undefined; this.first = undefined; this.second = undefined; this.ratio = undefined; } Vertex.prototype.clone = function(result) { if (!defined(result)) { result = new Vertex(); } result.uBuffer = this.uBuffer; result.vBuffer = this.vBuffer; result.heightBuffer = this.heightBuffer; result.normalBuffer = this.normalBuffer; result.index = this.index; result.first = this.first; result.second = this.second; result.ratio = this.ratio; return result; }; Vertex.prototype.initializeIndexed = function(uBuffer, vBuffer, heightBuffer, normalBuffer, index) { this.uBuffer = uBuffer; this.vBuffer = vBuffer; this.heightBuffer = heightBuffer; this.normalBuffer = normalBuffer; this.index = index; this.first = undefined; this.second = undefined; this.ratio = undefined; }; Vertex.prototype.initializeInterpolated = function(first, second, ratio) { this.vertexBuffer = undefined; this.index = undefined; this.newIndex = undefined; this.first = first; this.second = second; this.ratio = ratio; }; Vertex.prototype.initializeFromClipResult = function(clipResult, index, vertices) { var nextIndex = index + 1; if (clipResult[index] !== -1) { vertices[clipResult[index]].clone(this); } else { this.vertexBuffer = undefined; this.index = undefined; this.first = vertices[clipResult[nextIndex]]; ++nextIndex; this.second = vertices[clipResult[nextIndex]]; ++nextIndex; this.ratio = clipResult[nextIndex]; ++nextIndex; } return nextIndex; }; Vertex.prototype.getKey = function() { if (this.isIndexed()) { return this.index; } return JSON.stringify({ first : this.first.getKey(), second : this.second.getKey(), ratio : this.ratio }); }; Vertex.prototype.isIndexed = function() { return defined(this.index); }; Vertex.prototype.getH = function() { if (defined(this.index)) { return this.heightBuffer[this.index]; } return CesiumMath.lerp(this.first.getH(), this.second.getH(), this.ratio); }; Vertex.prototype.getU = function() { if (defined(this.index)) { return this.uBuffer[this.index]; } return CesiumMath.lerp(this.first.getU(), this.second.getU(), this.ratio); }; Vertex.prototype.getV = function() { if (defined(this.index)) { return this.vBuffer[this.index]; } return CesiumMath.lerp(this.first.getV(), this.second.getV(), this.ratio); }; var encodedScratch = new Cartesian2(); // An upsampled triangle may be clipped twice before it is assigned an index // In this case, we need a buffer to handle the recursion of getNormalX() and getNormalY(). var depth = -1; var cartesianScratch1 = [new Cartesian3(), new Cartesian3()]; var cartesianScratch2 = [new Cartesian3(), new Cartesian3()]; function lerpOctEncodedNormal(vertex, result) { ++depth; var first = cartesianScratch1[depth]; var second = cartesianScratch2[depth]; first = AttributeCompression.octDecode(vertex.first.getNormalX(), vertex.first.getNormalY(), first); second = AttributeCompression.octDecode(vertex.second.getNormalX(), vertex.second.getNormalY(), second); cartesian3Scratch = Cartesian3.lerp(first, second, vertex.ratio, cartesian3Scratch); Cartesian3.normalize(cartesian3Scratch, cartesian3Scratch); AttributeCompression.octEncode(cartesian3Scratch, result); --depth; return result; } Vertex.prototype.getNormalX = function() { if (defined(this.index)) { return this.normalBuffer[this.index * 2]; } encodedScratch = lerpOctEncodedNormal(this, encodedScratch); return encodedScratch.x; }; Vertex.prototype.getNormalY = function() { if (defined(this.index)) { return this.normalBuffer[this.index * 2 + 1]; } encodedScratch = lerpOctEncodedNormal(this, encodedScratch); return encodedScratch.y; }; var polygonVertices = []; polygonVertices.push(new Vertex()); polygonVertices.push(new Vertex()); polygonVertices.push(new Vertex()); polygonVertices.push(new Vertex()); function addClippedPolygon(uBuffer, vBuffer, heightBuffer, normalBuffer, indices, vertexMap, clipped, triangleVertices, hasVertexNormals) { if (clipped.length === 0) { return; } var numVertices = 0; var clippedIndex = 0; while (clippedIndex < clipped.length) { clippedIndex = polygonVertices[numVertices++].initializeFromClipResult(clipped, clippedIndex, triangleVertices); } for (var i = 0; i < numVertices; ++i) { var polygonVertex = polygonVertices[i]; if (!polygonVertex.isIndexed()) { var key = polygonVertex.getKey(); if (defined(vertexMap[key])) { polygonVertex.newIndex = vertexMap[key]; } else { var newIndex = uBuffer.length; uBuffer.push(polygonVertex.getU()); vBuffer.push(polygonVertex.getV()); heightBuffer.push(polygonVertex.getH()); if (hasVertexNormals) { normalBuffer.push(polygonVertex.getNormalX()); normalBuffer.push(polygonVertex.getNormalY()); } polygonVertex.newIndex = newIndex; vertexMap[key] = newIndex; } } else { polygonVertex.newIndex = vertexMap[polygonVertex.index]; polygonVertex.uBuffer = uBuffer; polygonVertex.vBuffer = vBuffer; polygonVertex.heightBuffer = heightBuffer; if (hasVertexNormals) { polygonVertex.normalBuffer = normalBuffer; } } } if (numVertices === 3) { // A triangle. indices.push(polygonVertices[0].newIndex); indices.push(polygonVertices[1].newIndex); indices.push(polygonVertices[2].newIndex); } else if (numVertices === 4) { // A quad - two triangles. indices.push(polygonVertices[0].newIndex); indices.push(polygonVertices[1].newIndex); indices.push(polygonVertices[2].newIndex); indices.push(polygonVertices[0].newIndex); indices.push(polygonVertices[2].newIndex); indices.push(polygonVertices[3].newIndex); } } return createTaskProcessorWorker(upsampleQuantizedTerrainMesh); });