public UUID SaveBitmap(Bitmap data, bool lossless, bool temporary) { AssetBase asset = new AssetBase(UUID.Random(), "MRMDynamicImage", AssetType.Texture, m_scene.RegionInfo.RegionID) { Data = OpenJPEG.EncodeFromImage(data, lossless), Description = "MRM Image", Flags = (temporary) ? AssetFlags.Temperary : 0 }; asset.FillHash(); asset.ID = m_scene.AssetService.Store(asset); return asset.ID; }
/// <summary> /// Update what the avatar is wearing using an item from their inventory. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> public void AvatarIsWearing(IClientAPI client, AvatarWearingArgs e) { IScenePresence sp = m_scene.GetScenePresence(client.AgentId); if (sp == null) { m_log.WarnFormat("[AvatarFactory]: AvatarIsWearing unable to find presence for {0}", client.AgentId); return; } m_log.DebugFormat("[AvatarFactory]: AvatarIsWearing called for {0}", client.AgentId); // operate on a copy of the appearance so we don't have to lock anything IAvatarAppearanceModule appearance = sp.RequestModuleInterface<IAvatarAppearanceModule> (); AvatarAppearance avatAppearance = new AvatarAppearance (appearance.Appearance, false); IOpenRegionSettingsModule module = m_scene.RequestModuleInterface<IOpenRegionSettingsModule> (); bool NeedsRebake = false; if (module != null && module.EnableTeenMode) { foreach (AvatarWearingArgs.Wearable wear in e.NowWearing) { if (wear.Type == 10 & wear.ItemID == UUID.Zero && module.DefaultUnderpants != UUID.Zero) { NeedsRebake = true; wear.ItemID = module.DefaultUnderpants; InventoryItemBase item = new InventoryItemBase(UUID.Random()); item.InvType = (int)InventoryType.Wearable; item.AssetType = (int)AssetType.Clothing; item.Name = "Default Underpants"; item.Folder = m_scene.InventoryService.GetFolderForType(client.AgentId, InventoryType.Wearable, AssetType.Clothing).ID; item.Owner = client.AgentId; item.CurrentPermissions = 0;//Locked item.CreatorId = UUID.Zero.ToString(); item.AssetID = module.DefaultUnderpants; client.SendInventoryItemCreateUpdate(item, 0); } else if (wear.Type == 10 & wear.ItemID == UUID.Zero) { NeedsRebake = true; InventoryItemBase item = new InventoryItemBase (UUID.Random ()); item.InvType = (int)InventoryType.Wearable; item.AssetType = (int)AssetType.Clothing; item.Name = "Default Underpants"; item.Folder = m_scene.InventoryService.GetFolderForType (client.AgentId, InventoryType.Wearable, AssetType.Clothing).ID; item.Owner = client.AgentId; item.CurrentPermissions = 0;//Locked if (m_underPantsUUID == UUID.Zero) { m_underPantsUUID = UUID.Random (); AssetBase asset = new AssetBase(m_underPantsUUID, "Default Underpants", AssetType.Clothing, UUID.Zero) {Data = Utils.StringToBytes(m_defaultUnderPants)}; asset.FillHash(); asset.ID = m_scene.AssetService.Store(asset); m_underPantsUUID = asset.ID; } item.CreatorId = UUID.Zero.ToString (); item.AssetID = m_underPantsUUID; m_scene.InventoryService.AddItem(item); client.SendInventoryItemCreateUpdate(item, 0); wear.ItemID = item.ID; } if (wear.Type == 11 && wear.ItemID == UUID.Zero && module.DefaultUndershirt != UUID.Zero) { NeedsRebake = true; wear.ItemID = module.DefaultUndershirt; InventoryItemBase item = new InventoryItemBase(UUID.Random()); item.InvType = (int)InventoryType.Wearable; item.AssetType = (int)AssetType.Clothing; item.Name = "Default Undershirt"; item.Folder = m_scene.InventoryService.GetFolderForType(client.AgentId, InventoryType.Wearable, AssetType.Clothing).ID; item.Owner = client.AgentId; item.CurrentPermissions = 0;//Locked item.CreatorId = UUID.Zero.ToString(); item.AssetID = module.DefaultUndershirt; client.SendInventoryItemCreateUpdate(item, 0); } else if (wear.Type == 11 & wear.ItemID == UUID.Zero) { NeedsRebake = true; InventoryItemBase item = new InventoryItemBase (UUID.Random()); item.InvType = (int)InventoryType.Wearable; item.AssetType = (int)AssetType.Clothing; item.Name = "Default Undershirt"; item.Folder = m_scene.InventoryService.GetFolderForType (client.AgentId, InventoryType.Wearable, AssetType.Clothing).ID; item.Owner = client.AgentId; item.CurrentPermissions = 0;//Locked if (m_underShirtUUID == UUID.Zero) { m_underShirtUUID = UUID.Random (); AssetBase asset = new AssetBase(m_underShirtUUID, "Default Undershirt", AssetType.Clothing, UUID.Zero) {Data = Utils.StringToBytes(m_defaultUnderShirt)}; asset.FillHash(); asset.ID = m_scene.AssetService.Store(asset); m_underShirtUUID = asset.ID; } item.CreatorId = UUID.Zero.ToString (); item.AssetID = m_underShirtUUID; m_scene.InventoryService.AddItem(item); client.SendInventoryItemCreateUpdate(item, 0); wear.ItemID = item.ID; } } } foreach (AvatarWearingArgs.Wearable wear in e.NowWearing) { if (wear.Type < AvatarWearable.MAX_WEARABLES) { /*if (incomingLinks.ContainsKey (wear.ItemID)) { wear.ItemID = incomingLinks[wear.ItemID]; }*/ avatAppearance.Wearables[wear.Type].Add (wear.ItemID, UUID.Zero); } } avatAppearance.GetAssetsFrom (appearance.Appearance); // This could take awhile since it needs to pull inventory SetAppearanceAssets(sp.UUID, ref avatAppearance); // could get fancier with the locks here, but in the spirit of "last write wins" // this should work correctly, also, we don't need to send the appearance here // since the "iswearing" will trigger a new set of visual param and baked texture changes // when those complete, the new appearance will be sent appearance.Appearance = avatAppearance; if (NeedsRebake) { //Tell the client about the new things it is wearing sp.ControllingClient.SendWearables (appearance.Appearance.Wearables, appearance.Appearance.Serial); //Then forcefully tell it to rebake for (int i = 0; i < appearance.Appearance.Texture.FaceTextures.Length; i++) { Primitive.TextureEntryFace face = (appearance.Appearance.Texture.FaceTextures[i]); if (face != null) sp.ControllingClient.SendRebakeAvatarTextures (face.TextureID); } } QueueAppearanceSave(sp.UUID); //Send the wearables HERE so that the client knows what it is wearing //sp.ControllingClient.SendWearables(sp.Appearance.Wearables, sp.Appearance.Serial); //Do not save or send the appearance! The client loops back and sends a bunch of SetAppearance // (handled above) and that takes care of it }
/// <summary> /// This method is called if a given model avatar name can not be found. If the external /// file has already been loaded once, then control returns immediately. If not, then it /// looks for a default appearance file. This file contains XML definitions of zero or more named /// avatars, each avatar can specify zero or more "outfits". Each outfit is a collection /// of items that together, define a particular ensemble for the avatar. Each avatar should /// indicate which outfit is the default, and this outfit will be automatically worn. The /// other outfits are provided to allow "real" avatars a way to easily change their outfits. /// </summary> private bool CreateDefaultAvatars() { // Only load once if (m_defaultAvatarsLoaded) { return false; } m_log.DebugFormat("[RADMIN] Creating default avatar entries"); m_defaultAvatarsLoaded = true; // Load processing starts here... try { string defaultAppearanceFileName = null; //m_config may be null if RemoteAdmin configuration secition is missing or disabled in Aurora.ini if (m_config != null) { defaultAppearanceFileName = m_config.GetString("default_appearance", "default_appearance.xml"); } if (File.Exists(defaultAppearanceFileName)) { XmlDocument doc = new XmlDocument(); string name = "*unknown*"; string email = "anon@anon"; uint regionXLocation = 1000; uint regionYLocation = 1000; string password = UUID.Random().ToString(); // No requirement to sign-in. UUID ID = UUID.Zero; AvatarAppearance avatarAppearance; XmlNodeList avatars; XmlNodeList assets; XmlNode perms = null; bool include = false; bool select = false; IScene scene = manager.CurrentOrFirstScene; IInventoryService inventoryService = scene.InventoryService; IAssetService assetService = scene.AssetService; doc.LoadXml(File.ReadAllText(defaultAppearanceFileName)); // Load up any included assets. Duplicates will be ignored assets = doc.GetElementsByTagName("RequiredAsset"); foreach (XmlNode assetNode in assets) { AssetBase asset = new AssetBase(UUID.Random(), GetStringAttribute(assetNode, "name", ""), (AssetType) SByte.Parse(GetStringAttribute(assetNode, "type", "")), UUID.Zero) { Description = GetStringAttribute(assetNode, "desc", ""), Data = Convert.FromBase64String(assetNode.InnerText), Flags = ((Boolean.Parse(GetStringAttribute(assetNode, "local", ""))) ? AssetFlags.Local : AssetFlags.Normal) | ((Boolean.Parse(GetStringAttribute(assetNode, "temporary", ""))) ? AssetFlags.Temperary : AssetFlags.Normal) }; asset.FillHash(); asset.ID = assetService.Store(asset); } avatars = doc.GetElementsByTagName("Avatar"); // The document may contain multiple avatars foreach (XmlElement avatar in avatars) { m_log.DebugFormat("[RADMIN] Loading appearance for {0}, gender = {1}", GetStringAttribute(avatar,"name","?"), GetStringAttribute(avatar,"gender","?")); // Create the user identified by the avatar entry try { // Only the name value is mandatory name = GetStringAttribute(avatar,"name",name); email = GetStringAttribute(avatar,"email",email); regionXLocation = GetUnsignedAttribute(avatar,"regx",regionXLocation); regionYLocation = GetUnsignedAttribute(avatar,"regy",regionYLocation); password = GetStringAttribute(avatar,"password",password); string[] names = name.Split(); UUID scopeID = scene.RegionInfo.ScopeID; UserAccount account = scene.UserAccountService.GetUserAccount(scopeID, names[0], names[1]); if (null == account) { account = CreateUser(scopeID, names[0], names[1], password, email); if (null == account) { m_log.ErrorFormat("[RADMIN] Avatar {0} {1} was not created", names[0], names[1]); return false; } } // Set home position GridRegion home = scene.GridService.GetRegionByPosition(scopeID, (int)(regionXLocation * Constants.RegionSize), (int)(regionYLocation * Constants.RegionSize)); if (null == home) { m_log.WarnFormat("[RADMIN]: Unable to set home region for newly created user account {0} {1}", names[0], names[1]); } else { IAgentInfoService agentInfoService = scene.RequestModuleInterface<IAgentInfoService>(); agentInfoService.SetHomePosition(account.PrincipalID.ToString(), home.RegionID, new Vector3(128, 128, 0), new Vector3(0, 1, 0)); m_log.DebugFormat("[RADMIN]: Set home region {0} for updated user account {1} {2}", home.RegionID, names[0], names[1]); } ID = account.PrincipalID; m_log.DebugFormat("[RADMIN] User {0}[{1}] created or retrieved", name, ID); include = true; } catch (Exception e) { m_log.DebugFormat("[RADMIN] Error creating user {0} : {1}", name, e.Message); include = false; } // OK, User has been created OK, now we can install the inventory. // First retrieve the current inventory (the user may already exist) // Note that althought he inventory is retrieved, the hierarchy has // not been interpreted at all. if (include) { // Setup for appearance processing AvatarData avatarData = scene.AvatarService.GetAvatar(ID); if (avatarData != null) avatarAppearance = avatarData.ToAvatarAppearance(ID); else avatarAppearance = new AvatarAppearance(); AvatarWearable[] wearables = avatarAppearance.Wearables; for (int i=0; i<wearables.Length; i++) { wearables[i] = new AvatarWearable(); } try { // m_log.DebugFormat("[RADMIN] {0} folders, {1} items in inventory", // uic.folders.Count, uic.items.Count); InventoryFolderBase clothingFolder = inventoryService.GetFolderForType (ID, InventoryType.Wearable, AssetType.Clothing); // This should *never* be the case if (clothingFolder == null || clothingFolder.Type != (short)AssetType.Clothing) { clothingFolder = new InventoryFolderBase(); clothingFolder.ID = UUID.Random(); clothingFolder.Name = "Clothing"; clothingFolder.Owner = ID; clothingFolder.Type = (short)AssetType.Clothing; clothingFolder.ParentID = inventoryService.GetRootFolder(ID).ID; clothingFolder.Version = 1; inventoryService.AddFolder(clothingFolder); // store base record m_log.ErrorFormat("[RADMIN] Created clothing folder for {0}/{1}", name, ID); } // OK, now we have an inventory for the user, read in the outfits from the // default appearance XMl file. XmlNodeList outfits = avatar.GetElementsByTagName("Ensemble"); InventoryFolderBase extraFolder; string outfitName; UUID assetid; foreach (XmlElement outfit in outfits) { m_log.DebugFormat("[RADMIN] Loading outfit {0} for {1}", GetStringAttribute(outfit,"name","?"), GetStringAttribute(avatar,"name","?")); outfitName = GetStringAttribute(outfit,"name",""); select = (GetStringAttribute(outfit,"default","no") == "yes"); // If the folder already exists, re-use it. The defaults may // change over time. Augment only. List<InventoryFolderBase> folders = inventoryService.GetFolderContent(ID, clothingFolder.ID).Folders; extraFolder = null; foreach (InventoryFolderBase folder in folders) { if (folder.Name == outfitName) { extraFolder = folder; break; } } // Otherwise, we must create the folder. if (extraFolder == null) { m_log.DebugFormat("[RADMIN] Creating outfit folder {0} for {1}", outfitName, name); extraFolder = new InventoryFolderBase(); extraFolder.ID = UUID.Random(); extraFolder.Name = outfitName; extraFolder.Owner = ID; extraFolder.Type = (short)AssetType.Clothing; extraFolder.Version = 1; extraFolder.ParentID = clothingFolder.ID; inventoryService.AddFolder(extraFolder); m_log.DebugFormat("[RADMIN] Adding outfile folder {0} to folder {1}", extraFolder.ID, clothingFolder.ID); } // Now get the pieces that make up the outfit XmlNodeList items = outfit.GetElementsByTagName("Item"); foreach (XmlElement item in items) { assetid = UUID.Zero; XmlNodeList children = item.ChildNodes; foreach (XmlNode child in children) { switch (child.Name) { case "Permissions" : m_log.DebugFormat("[RADMIN] Permissions specified"); perms = child; break; case "Asset" : assetid = new UUID(child.InnerText); break; } } InventoryItemBase inventoryItem = null; // Check if asset is in inventory already inventoryItem = null; List<InventoryItemBase> inventoryItems = inventoryService.GetFolderContent(ID, extraFolder.ID).Items; foreach (InventoryItemBase listItem in inventoryItems) { if (listItem.AssetID == assetid) { inventoryItem = listItem; break; } } // Create inventory item if (inventoryItem == null) { inventoryItem = new InventoryItemBase(UUID.Random(), ID); inventoryItem.Name = GetStringAttribute(item,"name",""); inventoryItem.Description = GetStringAttribute(item,"desc",""); inventoryItem.InvType = GetIntegerAttribute(item,"invtype",-1); inventoryItem.CreatorId = GetStringAttribute(item,"creatorid",""); inventoryItem.CreatorIdAsUuid = (UUID)GetStringAttribute(item,"creatoruuid",""); inventoryItem.NextPermissions = GetUnsignedAttribute(perms,"next",0x7fffffff); inventoryItem.CurrentPermissions = GetUnsignedAttribute(perms,"current",0x7fffffff); inventoryItem.BasePermissions = GetUnsignedAttribute(perms,"base",0x7fffffff); inventoryItem.EveryOnePermissions = GetUnsignedAttribute(perms,"everyone",0x7fffffff); inventoryItem.GroupPermissions = GetUnsignedAttribute(perms,"group",0x7fffffff); inventoryItem.AssetType = GetIntegerAttribute(item,"assettype",-1); inventoryItem.AssetID = assetid; // associated asset inventoryItem.GroupID = (UUID)GetStringAttribute(item,"groupid",""); inventoryItem.GroupOwned = (GetStringAttribute(item,"groupowned","false") == "true"); inventoryItem.SalePrice = GetIntegerAttribute(item,"saleprice",0); inventoryItem.SaleType = (byte)GetIntegerAttribute(item,"saletype",0); inventoryItem.Flags = GetUnsignedAttribute(item,"flags",0); inventoryItem.CreationDate = GetIntegerAttribute(item,"creationdate",Util.UnixTimeSinceEpoch()); inventoryItem.Folder = extraFolder.ID; // Parent folder ILLClientInventory inventoryModule = manager.CurrentOrFirstScene.RequestModuleInterface<ILLClientInventory>(); if (inventoryModule != null) inventoryModule.AddInventoryItem(inventoryItem); m_log.DebugFormat("[RADMIN] Added item {0} to folder {1}", inventoryItem.ID, extraFolder.ID); } // Attach item, if attachpoint is specified int attachpoint = GetIntegerAttribute(item,"attachpoint",0); if (attachpoint != 0) { avatarAppearance.SetAttachment(attachpoint, inventoryItem.ID, inventoryItem.AssetID); m_log.DebugFormat("[RADMIN] Attached {0}", inventoryItem.ID); } // Record whether or not the item is to be initially worn try { if (select && (GetStringAttribute(item, "wear", "false") == "true")) { avatarAppearance.Wearables[inventoryItem.Flags].Wear(inventoryItem.ID, inventoryItem.AssetID); } } catch (Exception e) { m_log.WarnFormat("[RADMIN] Error wearing item {0} : {1}", inventoryItem.ID, e.Message); } } // foreach item in outfit m_log.DebugFormat("[RADMIN] Outfit {0} load completed", outfitName); } // foreach outfit m_log.DebugFormat("[RADMIN] Inventory update complete for {0}", name); AvatarData avatarData2 = new AvatarData(avatarAppearance); scene.AvatarService.SetAvatar(ID, avatarData2); } catch (Exception e) { m_log.WarnFormat("[RADMIN] Inventory processing incomplete for user {0} : {1}", name, e.Message); } } // End of include } m_log.DebugFormat("[RADMIN] Default avatar loading complete"); } else { m_log.DebugFormat("[RADMIN] No default avatar information available"); return false; } } catch (Exception e) { m_log.WarnFormat("[RADMIN] Exception whilst loading default avatars ; {0}", e.Message); return false; } return true; }
/// <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(ITerrainChannel heightmap, UUID[] textureIDs, float[] startHeights, float[] heightRanges, Vector3d regionPosition, IAssetService assetService, bool textureTerrain) { Debug.Assert (textureIDs.Length == 4); Debug.Assert (startHeights.Length == 4); Debug.Assert (heightRanges.Length == 4); Bitmap[] detailTexture = new Bitmap[4]; if (textureTerrain) { // 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 if (assetService != null) { for (int i = 0; i < 4; i++) { AssetBase asset; UUID cacheID = UUID.Combine (TERRAIN_CACHE_MAGIC, textureIDs[i]); // The asset service does this check as well, so I don't think we need to do this. asset = assetService.GetCached (cacheID.ToString ()); if ((asset != null) && (asset.Data != null) && (asset.Data.Length != 0)) { 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 asset = assetService.Get (textureIDs[i].ToString ()); if (asset != null) { 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 = ImageUtils.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 (); } // Cache a PNG copy of this terrain texture AssetBase newAsset = new AssetBase { Data = data, Description = "PNG", Flags = AssetFlags.Collectable | AssetFlags.Temperary | AssetFlags.Local, ID = cacheID, Name = String.Empty, TypeString = "image/png" }; newAsset.FillHash(); newAsset.ID = assetService.Store(newAsset); } } } } #endregion Texture Fetching } // 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); } } else if (detailTexture[i].Width != 256 || detailTexture[i].Height != 256) { detailTexture[i] = ResizeBitmap (detailTexture[i], 256, 256); } } #region Layer Map int diff = (int)heightmap.Height / Constants.RegionSize; float[] layermap = new float[Constants.RegionSize * Constants.RegionSize]; for (int y = 0; y < heightmap.Height; y += diff) { for (int x = 0; x < heightmap.Height; x += diff) { int newX = x / diff; int newY = y / diff; float height = heightmap[newX, newY]; float pctX = (float)newX / 255f; float pctY = (float)newY / 255f; // Use bilinear interpolation between the four corners of start height and // height range to select the current values at this position float startHeight = ImageUtils.Bilinear ( startHeights[0], startHeights[2], startHeights[1], startHeights[3], pctX, pctY); startHeight = Utils.Clamp (startHeight, 0f, 255f); float heightRange = ImageUtils.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 + newX) * 0.20319f, ((float)regionPosition.Y + newY) * 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[newY * Constants.RegionSize + newX] = 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 < Constants.RegionSize; y++) { for (int x = 0; x < Constants.RegionSize; x++) { float layer = layermap[y * Constants.RegionSize + 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]); detailTexture[i].Dispose (); } } layermap = null; output.UnlockBits (outputData); // We generated the texture upside down, so flip it output.RotateFlip (RotateFlipType.RotateNoneFlipY); #endregion Texture Compositing return output; }