private Bitmap GetSculptMap(UUID textureID) { Bitmap sculptTexture = null; if (m_assetClient != null) { Asset textureAsset; if (m_assetClient.TryGetAsset(textureID, "image/x-j2c", out textureAsset)) { try { sculptTexture = (Bitmap)CSJ2K.J2kImage.FromBytes(textureAsset.Data); } catch (Exception ex) { m_log.Warn("Failed to decode sculpt texture " + textureAsset.ID + ": " + ex.Message); } } else { m_log.Warn("Failed to fetch sculpt texture asset " + textureID); } } return(sculptTexture); }
public static OSDMap SerializeAppearance(IAssetClient assetClient, Primitive.TextureEntry texture, byte[] visualParams) { // Fetch all of the texture assets Dictionary <UUID, byte[]> textures = new Dictionary <UUID, byte[]>(); for (int i = 0; i < texture.FaceTextures.Length; i++) { Primitive.TextureEntryFace face = texture.FaceTextures[i]; if (face != null && face.TextureID != AppearanceManager.DEFAULT_AVATAR_TEXTURE && !textures.ContainsKey(face.TextureID)) { // Fetch this texture Asset asset; if (assetClient.TryGetAsset(face.TextureID, "image/x-j2c", out asset)) { textures.Add(face.TextureID, asset.Data); } } } OSDMap assets = new OSDMap(textures.Count); foreach (KeyValuePair <UUID, byte[]> kvp in textures) { assets.Add(kvp.Key.ToString(), OSD.FromBinary(kvp.Value)); } return(new OSDMap { { "texture", texture.GetOSD() }, { "parameters", OSD.FromBinary(visualParams) }, { "assets", assets } }); }
private void GetTextureHandler(Capability cap, IHttpClientContext context, IHttpRequest request, IHttpResponse response) { // TODO: Change this to a config option const string REDIRECT_URL = null; // Try to parse the texture ID from the request URL NameValueCollection query = HttpUtility.ParseQueryString(request.Uri.Query); string textureStr = query.GetOne("texture_id"); UUID textureID; if (!String.IsNullOrEmpty(textureStr) && UUID.TryParse(textureStr, out textureID)) { Asset texture; if (!String.IsNullOrEmpty(REDIRECT_URL)) { // Only try to fetch locally cached textures. Misses are redirected if (m_assetClient.TryGetCachedAsset(textureID, "image/x-j2c", out texture)) { SendTexture(request, response, texture); } else { string textureUrl = REDIRECT_URL + textureID.ToString(); m_log.Debug("Redirecting texture request to " + textureUrl); response.Redirect(textureUrl); } } else { // Fetch locally or remotely. Misses return a 404 if (m_assetClient.TryGetAsset(textureID, "image/x-j2c", out texture)) { SendTexture(request, response, texture); } else { m_log.Warn("Texture " + textureID + " not found"); if (m_assetClient.TryGetCachedAsset(MISSING_IMAGE, "image/x-j2c", out texture)) { SendTexture(request, response, texture); } else { m_log.Warn("Missing image texture " + MISSING_IMAGE + " not found, returning a 404"); response.Status = System.Net.HttpStatusCode.NotFound; } } } } else { m_log.Warn("Failed to parse a texture_id from GetTexture request: " + request.Uri); } }
private void GetMeshHandler(Capability cap, IHttpClientContext context, IHttpRequest request, IHttpResponse response) { // TODO: Change this to a config option const string REDIRECT_URL = null; // Try to parse the mesh ID from the request URL NameValueCollection query = HttpUtility.ParseQueryString(request.Uri.Query); string meshStr = query.GetOne("mesh_id"); UUID meshID; if (!String.IsNullOrEmpty(meshStr) && UUID.TryParse(meshStr, out meshID)) { Asset mesh; if (!String.IsNullOrEmpty(REDIRECT_URL)) { // Only try to fetch locally cached meshes. Misses are redirected if (m_assetClient.TryGetCachedAsset(meshID, "application/vnd.ll.mesh", out mesh)) { SendMesh(request, response, mesh); } else { string meshUrl = REDIRECT_URL + meshID.ToString(); m_log.Debug("Redirecting mesh request to " + meshUrl); response.Redirect(meshUrl); } } else { // Fetch locally or remotely. Misses return a 404 if (m_assetClient.TryGetAsset(meshID, "application/vnd.ll.mesh", out mesh)) { SendMesh(request, response, mesh); } else { m_log.Warn("Mesh " + meshID + " not found, returning a 404"); response.Status = System.Net.HttpStatusCode.NotFound; } } } else { m_log.Warn("Failed to parse a mesh_id from GetMesh request: " + request.Uri); } }
private void TransferRequestHandler(Packet packet, LLAgent agent) { TransferRequestPacket request = (TransferRequestPacket)packet; ChannelType channel = (ChannelType)request.TransferInfo.ChannelType; SourceType source = (SourceType)request.TransferInfo.SourceType; if (channel == ChannelType.Asset) { if (source == SourceType.Asset) { // Parse the request UUID assetID = new UUID(request.TransferInfo.Params, 0); AssetType type = (AssetType)(sbyte)Utils.BytesToInt(request.TransferInfo.Params, 16); string contentType = LLUtil.LLAssetTypeToContentType((int)type); // Permission check if (!CanDownloadInventory(agent, type, assetID)) { TransferNotFound(agent, request.TransferInfo.TransferID, assetID, type); return; } // Check if we have this asset Asset asset; if (m_assets.TryGetAsset(assetID, contentType, out asset)) { TransferDownload(agent, request.TransferInfo.TransferID, assetID, type, asset); } else { TransferNotFound(agent, request.TransferInfo.TransferID, assetID, type); } } else if (source == SourceType.SimInventoryItem) { //UUID agentID = new UUID(request.TransferInfo.Params, 0); //UUID sessionID = new UUID(request.TransferInfo.Params, 16); //UUID ownerID = new UUID(request.TransferInfo.Params, 32); UUID taskID = new UUID(request.TransferInfo.Params, 48); UUID itemID = new UUID(request.TransferInfo.Params, 64); UUID assetID = new UUID(request.TransferInfo.Params, 80); AssetType type = (AssetType)(sbyte)Utils.BytesToInt(request.TransferInfo.Params, 96); string contentType = LLUtil.LLAssetTypeToContentType((int)type); if (taskID != UUID.Zero) { // Task (prim) inventory request permission check if (!CanDownloadTaskInventory(agent, type, taskID, itemID)) { TransferNotFound(agent, request.TransferInfo.TransferID, assetID, type); return; } } else { // Agent inventory request permission check if (!CanDownloadInventory(agent, type, assetID)) { TransferNotFound(agent, request.TransferInfo.TransferID, assetID, type); return; } } // Check if we have this asset Asset asset; if (m_assets.TryGetAsset(assetID, contentType, out asset)) { TransferDownload(agent, request.TransferInfo.TransferID, assetID, type, asset); } else { TransferNotFound(agent, request.TransferInfo.TransferID, assetID, type); } } else if (source == SourceType.SimEstate) { //UUID agentID = new UUID(request.TransferInfo.Params, 0); //UUID sessionID = new UUID(request.TransferInfo.Params, 16); //EstateAssetType type = (EstateAssetType)Utils.BytesToInt(request.TransferInfo.Params, 32); m_log.Warn("Don't know what to do with an estate asset transfer request"); } else { m_log.WarnFormat( "Received a TransferRequest that we don't know how to handle. Channel: {0}, Source: {1}", channel, source); } } else { m_log.WarnFormat( "Received a TransferRequest that we don't know how to handle. Channel: {0}, Source: {1}", channel, source); } }
/// <summary> /// Builds a composited terrain texture given the region texture /// and heightmap settings /// </summary> /// <param name="heightmap">Terrain heightmap</param> /// <param name="regionInfo">Region information including terrain texture parameters</param> /// <returns>A composited 256x256 RGB texture ready for rendering</returns> /// <remarks>Based on the algorithm described at http://opensimulator.org/wiki/Terrain_Splatting /// </remarks> public static Bitmap Splat(float[] heightmap, UUID[] textureIDs, float[] startHeights, float[] heightRanges, Vector3d regionPosition, IAssetClient assetClient) { Debug.Assert(heightmap.Length == 256 * 256); Debug.Assert(textureIDs.Length == 4); Debug.Assert(startHeights.Length == 4); Debug.Assert(heightRanges.Length == 4); // Swap empty terrain textureIDs with default IDs for (int i = 0; i < textureIDs.Length; i++) { if (textureIDs[i] == UUID.Zero) textureIDs[i] = DEFAULT_TERRAIN_DETAIL[i]; } #region Texture Fetching Bitmap[] detailTexture = new Bitmap[4]; if (assetClient != null) { for (int i = 0; i < 4; i++) { Asset asset; UUID cacheID = UUID.Combine(TEXTURE_CACHE_MAGIC, textureIDs[i]); // Try to fetch a cached copy of the decoded/resized version of this texture if (assetClient.TryGetCachedAsset(cacheID, "image/png", out asset)) { try { using (System.IO.MemoryStream stream = new System.IO.MemoryStream(asset.Data)) detailTexture[i] = (Bitmap)Image.FromStream(stream); } catch (Exception ex) { m_log.Warn("Failed to decode cached terrain texture " + cacheID + " (textureID: " + textureIDs[i] + "): " + ex.Message); } } if (detailTexture[i] == null) { // Try to fetch the original JPEG2000 texture, resize if needed, and cache as PNG if (assetClient.TryGetAsset(textureIDs[i], "image/x-j2c", out asset)) { try { detailTexture[i] = (Bitmap)CSJ2K.J2kImage.FromBytes(asset.Data); } catch (Exception ex) { m_log.Warn("Failed to decode terrain texture " + asset.ID + ": " + ex.Message); } } if (detailTexture[i] != null) { Bitmap bitmap = detailTexture[i]; // Make sure this texture is the correct size, otherwise resize if (bitmap.Width != 256 || bitmap.Height != 256) bitmap = Util.ResizeImage(bitmap, 256, 256); // Save the decoded and resized texture to the cache byte[] data; using (System.IO.MemoryStream stream = new System.IO.MemoryStream()) { bitmap.Save(stream, ImageFormat.Png); data = stream.ToArray(); } assetClient.StoreAsset( new Asset { ContentType = "image/png", CreationDate = DateTime.UtcNow, CreatorID = UUID.Zero, Data = data, ID = cacheID, Local = true, Temporary = true } ); } } } } // Fill in any missing textures with a solid color for (int i = 0; i < 4; i++) { if (detailTexture[i] == null) { // Create a solid color texture for this layer detailTexture[i] = new Bitmap(256, 256, PixelFormat.Format24bppRgb); using (Graphics gfx = Graphics.FromImage(detailTexture[i])) { using (SolidBrush brush = new SolidBrush(DEFAULT_TERRAIN_COLOR[i])) gfx.FillRectangle(brush, 0, 0, 256, 256); } } } #endregion Texture Fetching #region Layer Map float[] layermap = new float[256 * 256]; for (int y = 0; y < 256; y++) { for (int x = 0; x < 256; x++) { float height = heightmap[y * 256 + x]; float pctX = (float)x / 255f; float pctY = (float)y / 255f; // Use bilinear interpolation between the four corners of start height and // height range to select the current values at this position float startHeight = Util.Bilinear( startHeights[0], startHeights[2], startHeights[1], startHeights[3], pctX, pctY); startHeight = Utils.Clamp(startHeight, 0f, 255f); float heightRange = Util.Bilinear( heightRanges[0], heightRanges[2], heightRanges[1], heightRanges[3], pctX, pctY); heightRange = Utils.Clamp(heightRange, 0f, 255f); // Generate two frequencies of perlin noise based on our global position // The magic values were taken from http://opensimulator.org/wiki/Terrain_Splatting Vector3 vec = new Vector3 ( ((float)regionPosition.X + x) * 0.20319f, ((float)regionPosition.Y + y) * 0.20319f, height * 0.25f ); float lowFreq = Perlin.noise2(vec.X * 0.222222f, vec.Y * 0.222222f) * 6.5f; float highFreq = Perlin.turbulence2(vec.X, vec.Y, 2f) * 2.25f; float noise = (lowFreq + highFreq) * 2f; // Combine the current height, generated noise, start height, and height range parameters, then scale all of it float layer = ((height + noise - startHeight) / heightRange) * 4f; if (Single.IsNaN(layer)) layer = 0f; layermap[y * 256 + x] = Utils.Clamp(layer, 0f, 3f); } } #endregion Layer Map #region Texture Compositing Bitmap output = new Bitmap(256, 256, PixelFormat.Format24bppRgb); BitmapData outputData = output.LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb); unsafe { // Get handles to all of the texture data arrays BitmapData[] datas = new BitmapData[] { detailTexture[0].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[0].PixelFormat), detailTexture[1].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[1].PixelFormat), detailTexture[2].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[2].PixelFormat), detailTexture[3].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[3].PixelFormat) }; int[] comps = new int[] { (datas[0].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3, (datas[1].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3, (datas[2].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3, (datas[3].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3 }; for (int y = 0; y < 256; y++) { for (int x = 0; x < 256; x++) { float layer = layermap[y * 256 + x]; // Select two textures int l0 = (int)Math.Floor(layer); int l1 = Math.Min(l0 + 1, 3); byte* ptrA = (byte*)datas[l0].Scan0 + y * datas[l0].Stride + x * comps[l0]; byte* ptrB = (byte*)datas[l1].Scan0 + y * datas[l1].Stride + x * comps[l1]; byte* ptrO = (byte*)outputData.Scan0 + y * outputData.Stride + x * 3; float aB = *(ptrA + 0); float aG = *(ptrA + 1); float aR = *(ptrA + 2); float bB = *(ptrB + 0); float bG = *(ptrB + 1); float bR = *(ptrB + 2); float layerDiff = layer - l0; // Interpolate between the two selected textures *(ptrO + 0) = (byte)Math.Floor(aB + layerDiff * (bB - aB)); *(ptrO + 1) = (byte)Math.Floor(aG + layerDiff * (bG - aG)); *(ptrO + 2) = (byte)Math.Floor(aR + layerDiff * (bR - aR)); } } for (int i = 0; i < 4; i++) detailTexture[i].UnlockBits(datas[i]); } output.UnlockBits(outputData); // We generated the texture upside down, so flip it output.RotateFlip(RotateFlipType.RotateNoneFlipY); #endregion Texture Compositing return output; }
private void EnqueueRequest(LLAgent agent, UUID textureID, sbyte discardLevel, float priority, uint packetNumber, uint sequenceNumber) { J2KImage image; // Look up this texture download lock (m_priorityQueue) m_queuedTextures.TryGetValue(textureID, out image); if (image != null) { // Update for an existing texture request if (discardLevel == -1 && priority == 0f) { //m_log.Debug("[TEX]: (CAN) ID=" + textureID); try { lock (m_priorityQueue) { m_priorityQueue.Delete(image.PriorityQueueHandle); m_queuedTextures.Remove(textureID); } } catch (Exception) { } } else { //m_log.DebugFormat("[TEX]: (UPD) ID={0}: D={1}, S={2}, P={3}", textureID, discardLevel, packetNumber, priority); // Check the packet sequence to make sure this update isn't older than // one we've already received if (sequenceNumber > image.LastSequence) { // Update the sequence number of the last RequestImage packet image.LastSequence = sequenceNumber; //Update the requested discard level image.DiscardLevel = discardLevel; //Update the requested packet number image.StartPacket = Math.Max(1, packetNumber); //Update the requested priority image.Priority = priority; // Update the start/end offsets for this request image.UpdateOffsets(); UpdateImageInQueue(image); } } } else { // New texture request if (discardLevel == -1 && priority == 0f) { //m_log.DebugFormat("[TEX]: (IGN) ID={0}", textureID); } else { //m_log.DebugFormat("[TEX]: (NEW) ID={0}: D={1}, S={2}, P={3}", textureID, discardLevel, packetNumber, priority); Asset asset; if (m_assetClient.TryGetAsset(textureID, "image/x-j2c", out asset)) { image = new J2KImage(m_udp, asset, agent, discardLevel, Math.Max(1, packetNumber), priority); // Update the start/end offsets for this request image.UpdateOffsets(); // Add this download to the priority queue UpdateImageInQueue(image); } else { ImageNotInDatabasePacket missing = new ImageNotInDatabasePacket(); missing.ImageID.ID = textureID; m_udp.SendPacket(agent, missing, ThrottleCategory.Asset, true); } } } }
public bool RezScript(UUID sourceItemID, UUID sourceAssetID, ISceneEntity hostObject, int scriptStartParam) { if (m_assetClient == null) { m_log.Error("Cannot rez script " + sourceItemID + " without an IAssetClient"); return(false); } Asset sourceAsset, binaryAsset; // Try to fetch the script source code asset if (m_assetClient.TryGetAsset(sourceAssetID, "application/vnd.ll.lsltext", out sourceAsset)) { // The script binary assetID is the MD5 hash of the source to avoid lots of duplicate compiles UUID scriptBinaryAssetID = new UUID(Utils.MD5(sourceAsset.Data), 0); if (m_assetClient.TryGetAsset(scriptBinaryAssetID, "application/vnd.ll.lslbyte", out binaryAsset)) { m_log.Debug("Using existing compile for scriptID " + sourceItemID); // Create an AppDomain for this script and post the entry point event to the queue StartScript(sourceItemID, sourceAssetID, hostObject, binaryAsset.Data); } else { #region Compile Script ScriptCompiler converter = new ScriptCompiler(m_scene); string csText = "using System;\nusing System.Collections.Generic;\n" + "using OpenMetaverse;\nusing Simian.Scripting.Linden;\n\n" + "namespace SecondLife {\n public class Script : LSLScriptBase {\n" + converter.Convert(Encoding.UTF8.GetString(sourceAsset.Data)) + " }\n}\n"; string[] convertWarnings = converter.GetWarnings(); CompilerParameters parameters = new CompilerParameters(); parameters.IncludeDebugInformation = false; parameters.CompilerOptions = "/optimize"; parameters.ReferencedAssemblies.Add("OpenMetaverseTypes.dll"); parameters.ReferencedAssemblies.Add("Simian.Scripting.Linden.dll"); parameters.GenerateExecutable = false; parameters.GenerateInMemory = false; parameters.TreatWarningsAsErrors = false; CompilerResults results; lock (m_csCodeProvider) results = m_csCodeProvider.CompileAssemblyFromSource(parameters, csText); #endregion Compile Script if (convertWarnings.Length == 0 && !results.Errors.HasErrors) { #region Binary Storage // Get the bytecode of the compiled assembly byte[] scriptBinary = File.ReadAllBytes(results.PathToAssembly); File.Delete(results.PathToAssembly); // Save the assembly to the asset store m_assetClient.StoreAsset(new Asset { ContentType = "application/vnd.ll.lslbyte", CreatorID = hostObject.OwnerID, Data = scriptBinary, ID = scriptBinaryAssetID, Local = true, Temporary = true, CreationDate = DateTime.UtcNow }); #endregion Binary Storage // Create an AppDomain for this script and post the entry point event to the queue StartScript(sourceItemID, sourceAssetID, hostObject, scriptBinary); return(true); } else { #region Compile Error Handling int displayErrors = 5; string errtext = String.Empty; foreach (string warning in convertWarnings) { // Show 5 errors max if (displayErrors <= 0) { break; } --displayErrors; // Treat converter warnings as errors errtext += "Error: " + warning + "\n"; } foreach (CompilerError compErr in results.Errors) { // Show 5 errors max if (displayErrors <= 0) { break; } --displayErrors; string severity = compErr.IsWarning ? "Warning" : "Error"; KeyValuePair <int, int> lslPos = FindErrorPosition(compErr.Line, compErr.Column, converter.PositionMap); string text = compErr.ErrorText; // Use LSL type names text = ReplaceTypes(compErr.ErrorText); // The Second Life viewer's script editor begins // counting lines and columns at 0, so we subtract 1. errtext += String.Format("Line ({0},{1}): {4} {2}: {3}\n", lslPos.Key - 1, lslPos.Value - 1, compErr.ErrorNumber, text, severity); } IScenePresence presence; if (m_scene.TryGetPresence(hostObject.OwnerID, out presence)) { m_scene.PresenceAlert(this, presence, "Script saved with warnings, check debug window!"); } m_scene.EntityChat(this, hostObject, 0f, errtext, 0, EntityChatType.Debug); #endregion Compile Error Handling } } } else { m_log.Warn("Couldn't find script source asset " + sourceAssetID); } return(false); }
/// <summary> /// Builds a composited terrain texture given the region texture /// and heightmap settings /// </summary> /// <param name="heightmap">Terrain heightmap</param> /// <param name="regionInfo">Region information including terrain texture parameters</param> /// <returns>A composited 256x256 RGB texture ready for rendering</returns> /// <remarks>Based on the algorithm described at http://opensimulator.org/wiki/Terrain_Splatting /// </remarks> public static Bitmap Splat(float[] heightmap, UUID[] textureIDs, float[] startHeights, float[] heightRanges, Vector3d regionPosition, IAssetClient assetClient) { Debug.Assert(heightmap.Length == 256 * 256); Debug.Assert(textureIDs.Length == 4); Debug.Assert(startHeights.Length == 4); Debug.Assert(heightRanges.Length == 4); // Swap empty terrain textureIDs with default IDs for (int i = 0; i < textureIDs.Length; i++) { if (textureIDs[i] == UUID.Zero) { textureIDs[i] = DEFAULT_TERRAIN_DETAIL[i]; } } #region Texture Fetching Bitmap[] detailTexture = new Bitmap[4]; if (assetClient != null) { for (int i = 0; i < 4; i++) { Asset asset; UUID cacheID = UUID.Combine(TEXTURE_CACHE_MAGIC, textureIDs[i]); // Try to fetch a cached copy of the decoded/resized version of this texture if (assetClient.TryGetCachedAsset(cacheID, "image/png", out asset)) { try { using (System.IO.MemoryStream stream = new System.IO.MemoryStream(asset.Data)) detailTexture[i] = (Bitmap)Image.FromStream(stream); } catch (Exception ex) { m_log.Warn("Failed to decode cached terrain texture " + cacheID + " (textureID: " + textureIDs[i] + "): " + ex.Message); } } if (detailTexture[i] == null) { // Try to fetch the original JPEG2000 texture, resize if needed, and cache as PNG if (assetClient.TryGetAsset(textureIDs[i], "image/x-j2c", out asset)) { try { detailTexture[i] = (Bitmap)CSJ2K.J2kImage.FromBytes(asset.Data); } catch (Exception ex) { m_log.Warn("Failed to decode terrain texture " + asset.ID + ": " + ex.Message); } } if (detailTexture[i] != null) { Bitmap bitmap = detailTexture[i]; // Make sure this texture is the correct size, otherwise resize if (bitmap.Width != 256 || bitmap.Height != 256) { bitmap = Util.ResizeImage(bitmap, 256, 256); } // Save the decoded and resized texture to the cache byte[] data; using (System.IO.MemoryStream stream = new System.IO.MemoryStream()) { bitmap.Save(stream, ImageFormat.Png); data = stream.ToArray(); } assetClient.StoreAsset( new Asset { ContentType = "image/png", CreationDate = DateTime.UtcNow, CreatorID = UUID.Zero, Data = data, ID = cacheID, Local = true, Temporary = true } ); } } } } // Fill in any missing textures with a solid color for (int i = 0; i < 4; i++) { if (detailTexture[i] == null) { // Create a solid color texture for this layer detailTexture[i] = new Bitmap(256, 256, PixelFormat.Format24bppRgb); using (Graphics gfx = Graphics.FromImage(detailTexture[i])) { using (SolidBrush brush = new SolidBrush(DEFAULT_TERRAIN_COLOR[i])) gfx.FillRectangle(brush, 0, 0, 256, 256); } } } #endregion Texture Fetching #region Layer Map float[] layermap = new float[256 * 256]; for (int y = 0; y < 256; y++) { for (int x = 0; x < 256; x++) { float height = heightmap[y * 256 + x]; float pctX = (float)x / 255f; float pctY = (float)y / 255f; // Use bilinear interpolation between the four corners of start height and // height range to select the current values at this position float startHeight = Util.Bilinear( startHeights[0], startHeights[2], startHeights[1], startHeights[3], pctX, pctY); startHeight = Utils.Clamp(startHeight, 0f, 255f); float heightRange = Util.Bilinear( heightRanges[0], heightRanges[2], heightRanges[1], heightRanges[3], pctX, pctY); heightRange = Utils.Clamp(heightRange, 0f, 255f); // Generate two frequencies of perlin noise based on our global position // The magic values were taken from http://opensimulator.org/wiki/Terrain_Splatting Vector3 vec = new Vector3 ( ((float)regionPosition.X + x) * 0.20319f, ((float)regionPosition.Y + y) * 0.20319f, height * 0.25f ); float lowFreq = Perlin.noise2(vec.X * 0.222222f, vec.Y * 0.222222f) * 6.5f; float highFreq = Perlin.turbulence2(vec.X, vec.Y, 2f) * 2.25f; float noise = (lowFreq + highFreq) * 2f; // Combine the current height, generated noise, start height, and height range parameters, then scale all of it float layer = ((height + noise - startHeight) / heightRange) * 4f; if (Single.IsNaN(layer)) { layer = 0f; } layermap[y * 256 + x] = Utils.Clamp(layer, 0f, 3f); } } #endregion Layer Map #region Texture Compositing Bitmap output = new Bitmap(256, 256, PixelFormat.Format24bppRgb); BitmapData outputData = output.LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb); unsafe { // Get handles to all of the texture data arrays BitmapData[] datas = new BitmapData[] { detailTexture[0].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[0].PixelFormat), detailTexture[1].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[1].PixelFormat), detailTexture[2].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[2].PixelFormat), detailTexture[3].LockBits(new Rectangle(0, 0, 256, 256), ImageLockMode.ReadOnly, detailTexture[3].PixelFormat) }; int[] comps = new int[] { (datas[0].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3, (datas[1].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3, (datas[2].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3, (datas[3].PixelFormat == PixelFormat.Format32bppArgb) ? 4 : 3 }; for (int y = 0; y < 256; y++) { for (int x = 0; x < 256; x++) { float layer = layermap[y * 256 + x]; // Select two textures int l0 = (int)Math.Floor(layer); int l1 = Math.Min(l0 + 1, 3); byte *ptrA = (byte *)datas[l0].Scan0 + y * datas[l0].Stride + x * comps[l0]; byte *ptrB = (byte *)datas[l1].Scan0 + y * datas[l1].Stride + x * comps[l1]; byte *ptrO = (byte *)outputData.Scan0 + y * outputData.Stride + x * 3; float aB = *(ptrA + 0); float aG = *(ptrA + 1); float aR = *(ptrA + 2); float bB = *(ptrB + 0); float bG = *(ptrB + 1); float bR = *(ptrB + 2); float layerDiff = layer - l0; // Interpolate between the two selected textures *(ptrO + 0) = (byte)Math.Floor(aB + layerDiff * (bB - aB)); *(ptrO + 1) = (byte)Math.Floor(aG + layerDiff * (bG - aG)); *(ptrO + 2) = (byte)Math.Floor(aR + layerDiff * (bR - aR)); } } for (int i = 0; i < 4; i++) { detailTexture[i].UnlockBits(datas[i]); } } output.UnlockBits(outputData); // We generated the texture upside down, so flip it output.RotateFlip(RotateFlipType.RotateNoneFlipY); #endregion Texture Compositing return(output); }