protected override TerrainMeshData GenerateForLod(IntensityImage image, int downsample) { // Downsampling rate must be a power of 2. if (!MathUtils.IsPowerOfTwo(downsample)) { throw new Exception($"Downsample rate of {downsample} is not a power of 2."); } // Calculate image bounds based on UV bounds. int imageStartX = Mathf.RoundToInt(_uvBounds.U1 * image.Width / downsample); int imageEndX = Mathf.RoundToInt(_uvBounds.U2 * (image.Width / downsample - 1)); int imageStartY = Mathf.RoundToInt(_uvBounds.V1 * image.Height / downsample); int imageEndY = Mathf.RoundToInt(_uvBounds.V2 * (image.Height / downsample - 1)); // Each pixel in the selected area of the downsampled image represents a vertex. int lonVertCount = imageEndX - imageStartX + 1; int latVertCount = imageEndY - imageStartY + 1; float latIncrement = _boundingBox.LatSwing / (latVertCount - 1); float lonIncrement = _boundingBox.LonSwing / (lonVertCount - 1); Vector3[] verts = new Vector3[latVertCount * lonVertCount]; Vector2[] uvs = new Vector2[latVertCount * lonVertCount]; Vector3[] edgeVerts = new Vector3[4 * (latVertCount + lonVertCount - 2) + 2]; Vector2 latLongOffset = BoundingBoxUtils.MedianLatLon(_boundingBox); Vector3 min = new Vector3(float.PositiveInfinity, 0, 0); int yIndex = 0, vertexIndex = 0; for (float vy = _boundingBox.LatStart; yIndex < latVertCount; vy += latIncrement) { // The y-coordinate on the image that corresponds to the current row of vertices. // Note this is actually inverted since we are traversing from bottom up. int y = (latVertCount - yIndex - 1 + imageStartY) * downsample; // Create a new vertex using the latitude angle. The coordinates of this vertex // will serve as a base for all the other vertices of the same latitude. Vector3 baseLatVertex = GenerateBaseLatitudeVertex(vy); int xIndex = 0; for (float vx = _boundingBox.LonStart; xIndex < lonVertCount; vx += lonIncrement) { // The x-coordinate on the image that corresponds to the current vertex. int x = (xIndex + imageStartX) * downsample; // Get the raw intensity value from the image. float value = downsample == 1 ? image.GetPixel(x, y) : image.GetCenteredAverage(x, y, downsample + 1); // Scale the intensity value by the height scale, and // then add it to the radius to get the final "height". float height = value * _metadata.HeightScale + _metadata.Radius; Vector3 vertex = GenerateVertex(height * baseLatVertex, vx, latLongOffset, _metadata.Radius); // Keep track of minimum; this will be used later to position the terrain on the table-top. if (vertex.x < min.x) { min = vertex; } // Add to edge vertices if (yIndex == 0) { edgeVerts[xIndex] = vertex; } else if (xIndex == lonVertCount - 1) { edgeVerts[lonVertCount + yIndex - 1] = vertex; } else if (yIndex == latVertCount - 1) { edgeVerts[2 * (lonVertCount - 1) + latVertCount - xIndex - 1] = vertex; } else if (xIndex == 0) { edgeVerts[2 * (lonVertCount + latVertCount - 2) - yIndex] = vertex; } verts[vertexIndex] = vertex; uvs[vertexIndex] = GenerateUVCoord(xIndex, yIndex, lonVertCount, latVertCount, _uvBounds); xIndex++; vertexIndex++; } yIndex++; } // Finish generating the data for the terrain edge. ProcessEdgeVertices(edgeVerts, min.x); return(new TerrainMeshData() { Vertices = verts, TexCoords = uvs, Triangles = GenerateTriangles(lonVertCount, latVertCount), ExtraVertices = edgeVerts, ExtraTriangles = GenerateTriangles(edgeVerts.Length / 2, 2, true), MinimumVertex = min }); }
protected override TerrainMeshData GenerateForLod(IntensityImage image, int downsample = 1) { // Downsampling rate must be a power of 2. if (!MathUtils.IsPowerOfTwo(downsample)) { throw new Exception($"Downsample rate of {downsample} is not a power of 2."); } // Vertex count for the latitude is the same as the downsampled texture height. // However, we need to generate an extra set of vertices in the longitude // direction to complete the loop around. We cannot simply reuse the first // vertices of of the loop, due to the start and end having different UV // coordinates despite having same world coordinates. int lonVertCount = image.Width / downsample + 1; int latVertCount = image.Height / downsample; Debug.Log(lonVertCount + ", " + latVertCount); Vector3[] verts = new Vector3[lonVertCount * latVertCount]; Vector2[] uvs = new Vector2[lonVertCount * latVertCount]; // Vertex counter int vertexIndex = 0; // Calculate the incretmental step sizes of the latitude // and longitude here for potential performance increase. float latStepSize = Mathf.PI / (latVertCount - 1); float lonStepSize = 360.0f / (lonVertCount - 1); // Iterate through the rows of vertices. for (int vy = 0; vy < latVertCount; vy++) { // The y-coordinate on the image that corresponds to the current row of vertices. int y = vy * downsample; // Iterate through each vertex in the row of verticies. // Calculate the actual angle of the latitude. float latAng = latStepSize * vy + Mathf.PI / 2; // Create a new vertex using the latitude angle. The coordinates of this // vertex will serve as a base for all the other vertices in this latitude. Vector3 baseLatVertex = new Vector3(Mathf.Cos(latAng), Mathf.Sin(latAng), 0); // Iterate through each vertex in the row of verticies. // Traverse backwards in order to get correct orientation of texture and normals. for (int vx = lonVertCount - 1; vx >= 0; vx--) { // The x-coordinate on the image that corresponds to the current vertex. int x = vx * downsample; // Get the raw intensity value from the image. float value = downsample == 1 ? image.GetPixel(x, y) : image.GetCenteredAverage(x, y, downsample + 1); // Scale the intensity value by the height scale, and // then add it to the radius to get the final "height". float height = value * _metadata.HeightScale + _metadata.Radius; // Longitude is offset by 90 degrees so that the foward vector is at 0,0 lat and long. verts[vertexIndex] = Quaternion.Euler(0, -90 - vx * lonStepSize, 0) * (height * baseLatVertex); uvs[vertexIndex] = MeshGenerationUtils.GenerateUVCoord(vx, vy, lonVertCount, latVertCount); vertexIndex++; } } return(new TerrainMeshData() { Vertices = verts, TexCoords = uvs, Triangles = MeshGenerationUtils.GenerateTriangles(lonVertCount, latVertCount) }); }
public static IntensityImage ToIntensityImage(TiffImage tiff) { TiffMetadata metadata = tiff.Metadata; // Check image format. if (metadata.SamplesPerPixel != 1) { throw new Exception("Color source cannot be converted into intensity image. Use ToRGBAImage() instead."); } // Create an Image object to store the result. IntensityImage result = new IntensityImage(metadata.Width, metadata.Height); // Tiled encoding... if (metadata.Tiled) { Vector2Int tilesAcrossImage = new Vector2Int( Mathf.CeilToInt(metadata.Width / (float)metadata.TileWidth), Mathf.CeilToInt(metadata.Height / (float)metadata.TileHeight) ); // Byte array for buffering the bytes read from each tile. byte[] tileBytes = new byte[metadata.TileSize]; // Float array for buffering the intensity values of each tile. float[] values = new float[metadata.TileWidth * metadata.TileHeight]; // Iterate through each tile. for (int ty = 0; ty < tilesAcrossImage.y; ty++) { // The y-coordinate of the tile's top row of pixels on the image. int y = ty * metadata.TileHeight; for (int tx = 0; tx < tilesAcrossImage.x; tx++) { // The x-coordinate of the tile's left column of pixels on the image. int x = tx * metadata.TileWidth; // Read bytes from tile and convert them to pixel values. tiff.ReadTile(tileBytes, 0, x, y, 0, 0); TiffUtils.BytesToFloat(tileBytes, values); // Iterate through the intensity values in the tile. for (int i = 0; i < values.Length; i++) { // Calculate the x and y coordinate relative to the tile // based on the index of the pixel within the tile. Vector2Int tilePixel = ImageUtils.IndexToCoordinates(i, metadata.TileWidth); // Update the Image object with the pixel value. result.SetPixel(tilePixel.x + x, tilePixel.y + y, values[i]); } } } } // Scanline encoding... else { // Byte array for buffering the bytes read from each scanline. byte[] scanlineBytes = new byte[metadata.ScanlineSize]; // Float array for buffering the intensity values of each pixels in a scanline. float[] values = new float[metadata.Width]; // Iterate through all the scanlines. for (int y = 0; y < metadata.Height; y++) { // Read bytes from scanline and convert them to pixel values. tiff.ReadScanline(scanlineBytes, y); TiffUtils.BytesToFloat(scanlineBytes, values); // Iterate through all the pixel values in the scanline. for (int x = 0; x < metadata.Width; x++) { // Update the Image object with the pixel value. result.SetPixel(x, y, values[x]); } } } return(result); }
/* * TODO Modify the implementations of the function such that * they generate all the LODs at the same time, instead of * having to read the image data once for each LOD. */ protected abstract TerrainMeshData GenerateForLod(IntensityImage image, int downsample);