diff --git a/OpenMetaverse/AssetManager.cs b/OpenMetaverse/AssetManager.cs
index 131cf258..063c8c46 100644
--- a/OpenMetaverse/AssetManager.cs
+++ b/OpenMetaverse/AssetManager.cs
@@ -1475,316 +1475,4 @@ namespace OpenMetaverse
#endregion Image Callbacks
}
-
- #region Texture Cache
- ///
- /// Class that handles the local image cache
- ///
- public class TextureCache
- {
- private GridClient Client;
- private Thread cleanerThread;
- private System.Timers.Timer cleanerTimer;
- private double pruneInterval = 1000 * 60 * 5;
-
- ///
- /// Allows setting weather to periodicale prune the cache if it grows too big
- /// Default is enabled, when caching is enabled
- ///
- public bool AutoPruneEnabled
- {
- set {
- if (!Operational()) {
- return;
- } else {
- cleanerTimer.Enabled = value;
- }
- }
- get { return cleanerTimer.Enabled;}
- }
-
- ///
- /// How long (in ms) between cache checks (default is 5 min.)
- ///
- public double AutoPruneInterval
- {
- get { return pruneInterval; }
- set
- {
- pruneInterval = value;
- cleanerTimer.Interval = pruneInterval;
- }
- }
-
- ///
- /// Default constructor
- ///
- /// A reference to the GridClient object
- public TextureCache(GridClient client)
- {
- Client = client;
- cleanerTimer = new System.Timers.Timer(pruneInterval);
- cleanerTimer.Elapsed += new System.Timers.ElapsedEventHandler(cleanerTimer_Elapsed);
- if (Operational()) {
- cleanerTimer.Enabled = true;
- } else {
- cleanerTimer.Enabled = false;
- }
- }
-
- ///
- /// Return bytes read from the local image cache, null if it does not exist
- ///
- /// UUID of the image we want to get
- /// Raw bytes of the image, or null on failure
- public byte[] GetCachedImageBytes(UUID imageID)
- {
- if (!Operational()) {
- return null;
- }
- try {
- Logger.DebugLog("Reading " + FileName(imageID) + " from texture cache.");
- byte[] data = File.ReadAllBytes(FileName(imageID));
- return data;
- } catch (Exception ex) {
- Logger.Log("Failed reading image from cache (" + ex.Message + ")", Helpers.LogLevel.Warning, Client);
- return null;
- }
- }
-
- ///
- /// Returns ImageDownload object of the
- /// image from the local image cache, null if it does not exist
- ///
- /// UUID of the image we want to get
- /// ImageDownload object containing the image, or null on failure
- public ImageDownload GetCachedImage(UUID imageID)
- {
- if (!Operational())
- return null;
-
- byte[] imageData = GetCachedImageBytes(imageID);
- if (imageData == null)
- return null;
- ImageDownload transfer = new ImageDownload();
- transfer.AssetType = AssetType.Texture;
- transfer.ID = imageID;
- transfer.Simulator = Client.Network.CurrentSim;
- transfer.Size = imageData.Length;
- transfer.Success = true;
- transfer.Transferred = imageData.Length;
- transfer.AssetData = imageData;
- return transfer;
- }
-
- ///
- /// Constructs a file name of the cached image
- ///
- /// UUID of the image
- /// String with the file name of the cahced image
- private string FileName(UUID imageID)
- {
- return Client.Settings.TEXTURE_CACHE_DIR + Path.DirectorySeparatorChar + imageID.ToString();
- }
-
- ///
- /// Saves an image to the local cache
- ///
- /// UUID of the image
- /// Raw bytes the image consists of
- /// Weather the operation was successfull
- public bool SaveImageToCache(UUID imageID, byte[] imageData)
- {
- if (!Operational()) {
- return false;
- }
-
- try {
- Logger.DebugLog("Saving " + FileName(imageID) + " to texture cache.", Client);
-
- if (!Directory.Exists(Client.Settings.TEXTURE_CACHE_DIR)) {
- Directory.CreateDirectory(Client.Settings.TEXTURE_CACHE_DIR);
- }
-
- File.WriteAllBytes(FileName(imageID), imageData);
- } catch (Exception ex) {
- Logger.Log("Failed saving image to cache (" + ex.Message + ")", Helpers.LogLevel.Warning, Client);
- return false;
- }
-
- return true;
- }
-
- ///
- /// Get the file name of the asset stored with gived UUID
- ///
- /// UUID of the image
- /// Null if we don't have that UUID cached on disk, file name if found in the cache folder
- public string ImageFileName(UUID imageID)
- {
- if (!Operational())
- {
- return null;
- }
-
- string fileName = FileName(imageID);
-
- if (File.Exists(fileName))
- return fileName;
- else
- return null;
- }
-
- ///
- /// Checks if the image exists in the local cache
- ///
- /// UUID of the image
- /// True is the image is stored in the cache, otherwise false
- public bool HasImage(UUID imageID)
- {
- if (!Operational()) {
- return false;
- }
- return File.Exists(FileName(imageID));
- }
-
- ///
- /// Wipes out entire cache
- ///
- public void Clear()
- {
- string cacheDir = Client.Settings.TEXTURE_CACHE_DIR;
- if (!Directory.Exists(cacheDir)) {
- return;
- }
-
- DirectoryInfo di = new DirectoryInfo(cacheDir);
- // We save file with UUID as file name, only delete those
- FileInfo[] files = di.GetFiles("????????-????-????-????-????????????", SearchOption.TopDirectoryOnly);
-
- int num = 0;
- foreach (FileInfo file in files) {
- file.Delete();
- ++num;
- }
-
- Logger.Log("Wiped out " + num + " files from the cache directory.", Helpers.LogLevel.Debug);
- }
-
- ///
- /// Brings cache size to the 90% of the max size
- ///
- public void Prune()
- {
- string cacheDir = Client.Settings.TEXTURE_CACHE_DIR;
- if (!Directory.Exists(cacheDir)) {
- return;
- }
- DirectoryInfo di = new DirectoryInfo(cacheDir);
- // We save file with UUID as file name, only count those
- FileInfo[] files = di.GetFiles("????????-????-????-????-????????????", SearchOption.TopDirectoryOnly);
-
- long size = GetFileSize(files);
-
- if (size > Client.Settings.TEXTURE_CACHE_MAX_SIZE) {
- Array.Sort(files, new SortFilesByAccesTimeHelper());
- long targetSize = (long)(Client.Settings.TEXTURE_CACHE_MAX_SIZE * 0.9);
- int num = 0;
- foreach (FileInfo file in files) {
- ++num;
- size -= file.Length;
- file.Delete();
- if (size < targetSize) {
- break;
- }
- }
- Logger.Log(num + " files deleted from the cache, cache size now: " + NiceFileSize(size), Helpers.LogLevel.Debug);
- } else {
- Logger.Log("Cache size is " + NiceFileSize(size) + ", file deletion not needed", Helpers.LogLevel.Debug);
- }
-
- }
-
- ///
- /// Asynchronously brings cache size to the 90% of the max size
- ///
- public void BeginPrune()
- {
- // Check if the background cache cleaning thread is active first
- if (cleanerThread != null && cleanerThread.IsAlive) {
- return;
- }
-
- lock (this) {
- cleanerThread = new Thread(new ThreadStart(this.Prune));
- cleanerThread.IsBackground = true;
- cleanerThread.Start();
- }
- }
-
- ///
- /// Adds up file sizes passes in a FileInfo array
- ///
- long GetFileSize(FileInfo[] files)
- {
- long ret = 0;
- foreach (FileInfo file in files) {
- ret += file.Length;
- }
- return ret;
- }
-
- ///
- /// Checks whether caching is enabled
- ///
- private bool Operational()
- {
- return Client.Settings.USE_TEXTURE_CACHE;
- }
-
- ///
- /// Periodically prune the cache
- ///
- private void cleanerTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
- {
- BeginPrune();
- }
-
- ///
- /// Nicely formats file sizes
- ///
- /// Byte size we want to output
- /// String with humanly readable file size
- private string NiceFileSize(long byteCount)
- {
- string size = "0 Bytes";
- if (byteCount >= 1073741824)
- size = String.Format("{0:##.##}", byteCount / 1073741824) + " GB";
- else if (byteCount >= 1048576)
- size = String.Format("{0:##.##}", byteCount / 1048576) + " MB";
- else if (byteCount >= 1024)
- size = String.Format("{0:##.##}", byteCount / 1024) + " KB";
- else if (byteCount > 0 && byteCount < 1024)
- size = byteCount.ToString() + " Bytes";
-
- return size;
- }
-
- ///
- /// Helper class for sorting files by their last accessed time
- ///
- private class SortFilesByAccesTimeHelper : IComparer
- {
- int IComparer.Compare(FileInfo f1, FileInfo f2)
- {
- if (f1.LastAccessTime > f2.LastAccessTime)
- return 1;
- if (f1.LastAccessTime < f2.LastAccessTime)
- return -1;
- else
- return 0;
- }
- }
- }
- #endregion
}
diff --git a/OpenMetaverse/Avatar.cs b/OpenMetaverse/Avatar.cs
index e440fdd0..8bbf8487 100644
--- a/OpenMetaverse/Avatar.cs
+++ b/OpenMetaverse/Avatar.cs
@@ -215,6 +215,36 @@ namespace OpenMetaverse
#region Properties
+ /// First name
+ public string FirstName
+ {
+ get
+ {
+ for (int i = 0; i < NameValues.Length; i++)
+ {
+ if (NameValues[i].Name == "FirstName" && NameValues[i].Type == NameValue.ValueType.String)
+ return (string)NameValues[i].Value;
+ }
+
+ return String.Empty;
+ }
+ }
+
+ /// Last name
+ public string LastName
+ {
+ get
+ {
+ for (int i = 0; i < NameValues.Length; i++)
+ {
+ if (NameValues[i].Name == "LastName" && NameValues[i].Type == NameValue.ValueType.String)
+ return (string)NameValues[i].Value;
+ }
+
+ return String.Empty;
+ }
+ }
+
/// Full name
public string Name
{
diff --git a/OpenMetaverse/TerrainCompressor.cs b/OpenMetaverse/TerrainCompressor.cs
index 5a432881..e7618b49 100644
--- a/OpenMetaverse/TerrainCompressor.cs
+++ b/OpenMetaverse/TerrainCompressor.cs
@@ -148,6 +148,32 @@ namespace OpenMetaverse
return layer;
}
+ public static LayerDataPacket CreateLandPacket(float[] patchData, int x, int y)
+ {
+ LayerDataPacket layer = new LayerDataPacket();
+ layer.LayerID.Type = (byte)TerrainPatch.LayerType.Land;
+
+ TerrainPatch.GroupHeader header = new TerrainPatch.GroupHeader();
+ header.Stride = STRIDE;
+ header.PatchSize = 16;
+ header.Type = TerrainPatch.LayerType.Land;
+
+ byte[] data = new byte[1536];
+ BitPack bitpack = new BitPack(data, 0);
+ bitpack.PackBits(header.Stride, 16);
+ bitpack.PackBits(header.PatchSize, 8);
+ bitpack.PackBits((int)header.Type, 8);
+
+ CreatePatch(bitpack, patchData, x, y);
+
+ bitpack.PackBits(END_OF_PATCHES, 8);
+
+ layer.LayerData.Data = new byte[bitpack.BytePos + 1];
+ Buffer.BlockCopy(bitpack.Data, 0, layer.LayerData.Data, 0, bitpack.BytePos + 1);
+
+ return layer;
+ }
+
public static void CreatePatch(BitPack output, float[] patchData, int x, int y)
{
if (patchData.Length != 16 * 16)
diff --git a/OpenMetaverse/TextureCache.cs b/OpenMetaverse/TextureCache.cs
new file mode 100644
index 00000000..a543cd8b
--- /dev/null
+++ b/OpenMetaverse/TextureCache.cs
@@ -0,0 +1,371 @@
+/*
+ * Copyright (c) 2008, openmetaverse.org
+ * All rights reserved.
+ *
+ * - Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Neither the name of the openmetaverse.org nor the names
+ * of its contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+
+namespace OpenMetaverse
+{
+ ///
+ /// Class that handles the local image cache
+ ///
+ public class TextureCache
+ {
+ private GridClient Client;
+ private Thread cleanerThread;
+ private System.Timers.Timer cleanerTimer;
+ private double pruneInterval = 1000 * 60 * 5;
+
+ ///
+ /// Allows setting weather to periodicale prune the cache if it grows too big
+ /// Default is enabled, when caching is enabled
+ ///
+ public bool AutoPruneEnabled
+ {
+ set
+ {
+ if (!Operational())
+ {
+ return;
+ }
+ else
+ {
+ cleanerTimer.Enabled = value;
+ }
+ }
+ get { return cleanerTimer.Enabled; }
+ }
+
+ ///
+ /// How long (in ms) between cache checks (default is 5 min.)
+ ///
+ public double AutoPruneInterval
+ {
+ get { return pruneInterval; }
+ set
+ {
+ pruneInterval = value;
+ cleanerTimer.Interval = pruneInterval;
+ }
+ }
+
+ ///
+ /// Default constructor
+ ///
+ /// A reference to the GridClient object
+ public TextureCache(GridClient client)
+ {
+ Client = client;
+ cleanerTimer = new System.Timers.Timer(pruneInterval);
+ cleanerTimer.Elapsed += new System.Timers.ElapsedEventHandler(cleanerTimer_Elapsed);
+ if (Operational())
+ {
+ cleanerTimer.Enabled = true;
+ }
+ else
+ {
+ cleanerTimer.Enabled = false;
+ }
+ }
+
+ ///
+ /// Return bytes read from the local image cache, null if it does not exist
+ ///
+ /// UUID of the image we want to get
+ /// Raw bytes of the image, or null on failure
+ public byte[] GetCachedImageBytes(UUID imageID)
+ {
+ if (!Operational())
+ {
+ return null;
+ }
+ try
+ {
+ Logger.DebugLog("Reading " + FileName(imageID) + " from texture cache.");
+ byte[] data = File.ReadAllBytes(FileName(imageID));
+ return data;
+ }
+ catch (Exception ex)
+ {
+ Logger.Log("Failed reading image from cache (" + ex.Message + ")", Helpers.LogLevel.Warning, Client);
+ return null;
+ }
+ }
+
+ ///
+ /// Returns ImageDownload object of the
+ /// image from the local image cache, null if it does not exist
+ ///
+ /// UUID of the image we want to get
+ /// ImageDownload object containing the image, or null on failure
+ public ImageDownload GetCachedImage(UUID imageID)
+ {
+ if (!Operational())
+ return null;
+
+ byte[] imageData = GetCachedImageBytes(imageID);
+ if (imageData == null)
+ return null;
+ ImageDownload transfer = new ImageDownload();
+ transfer.AssetType = AssetType.Texture;
+ transfer.ID = imageID;
+ transfer.Simulator = Client.Network.CurrentSim;
+ transfer.Size = imageData.Length;
+ transfer.Success = true;
+ transfer.Transferred = imageData.Length;
+ transfer.AssetData = imageData;
+ return transfer;
+ }
+
+ ///
+ /// Constructs a file name of the cached image
+ ///
+ /// UUID of the image
+ /// String with the file name of the cahced image
+ private string FileName(UUID imageID)
+ {
+ return Client.Settings.TEXTURE_CACHE_DIR + Path.DirectorySeparatorChar + imageID.ToString();
+ }
+
+ ///
+ /// Saves an image to the local cache
+ ///
+ /// UUID of the image
+ /// Raw bytes the image consists of
+ /// Weather the operation was successfull
+ public bool SaveImageToCache(UUID imageID, byte[] imageData)
+ {
+ if (!Operational())
+ {
+ return false;
+ }
+
+ try
+ {
+ Logger.DebugLog("Saving " + FileName(imageID) + " to texture cache.", Client);
+
+ if (!Directory.Exists(Client.Settings.TEXTURE_CACHE_DIR))
+ {
+ Directory.CreateDirectory(Client.Settings.TEXTURE_CACHE_DIR);
+ }
+
+ File.WriteAllBytes(FileName(imageID), imageData);
+ }
+ catch (Exception ex)
+ {
+ Logger.Log("Failed saving image to cache (" + ex.Message + ")", Helpers.LogLevel.Warning, Client);
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Get the file name of the asset stored with gived UUID
+ ///
+ /// UUID of the image
+ /// Null if we don't have that UUID cached on disk, file name if found in the cache folder
+ public string ImageFileName(UUID imageID)
+ {
+ if (!Operational())
+ {
+ return null;
+ }
+
+ string fileName = FileName(imageID);
+
+ if (File.Exists(fileName))
+ return fileName;
+ else
+ return null;
+ }
+
+ ///
+ /// Checks if the image exists in the local cache
+ ///
+ /// UUID of the image
+ /// True is the image is stored in the cache, otherwise false
+ public bool HasImage(UUID imageID)
+ {
+ if (!Operational())
+ {
+ return false;
+ }
+ return File.Exists(FileName(imageID));
+ }
+
+ ///
+ /// Wipes out entire cache
+ ///
+ public void Clear()
+ {
+ string cacheDir = Client.Settings.TEXTURE_CACHE_DIR;
+ if (!Directory.Exists(cacheDir))
+ {
+ return;
+ }
+
+ DirectoryInfo di = new DirectoryInfo(cacheDir);
+ // We save file with UUID as file name, only delete those
+ FileInfo[] files = di.GetFiles("????????-????-????-????-????????????", SearchOption.TopDirectoryOnly);
+
+ int num = 0;
+ foreach (FileInfo file in files)
+ {
+ file.Delete();
+ ++num;
+ }
+
+ Logger.Log("Wiped out " + num + " files from the cache directory.", Helpers.LogLevel.Debug);
+ }
+
+ ///
+ /// Brings cache size to the 90% of the max size
+ ///
+ public void Prune()
+ {
+ string cacheDir = Client.Settings.TEXTURE_CACHE_DIR;
+ if (!Directory.Exists(cacheDir))
+ {
+ return;
+ }
+ DirectoryInfo di = new DirectoryInfo(cacheDir);
+ // We save file with UUID as file name, only count those
+ FileInfo[] files = di.GetFiles("????????-????-????-????-????????????", SearchOption.TopDirectoryOnly);
+
+ long size = GetFileSize(files);
+
+ if (size > Client.Settings.TEXTURE_CACHE_MAX_SIZE)
+ {
+ Array.Sort(files, new SortFilesByAccesTimeHelper());
+ long targetSize = (long)(Client.Settings.TEXTURE_CACHE_MAX_SIZE * 0.9);
+ int num = 0;
+ foreach (FileInfo file in files)
+ {
+ ++num;
+ size -= file.Length;
+ file.Delete();
+ if (size < targetSize)
+ {
+ break;
+ }
+ }
+ Logger.Log(num + " files deleted from the cache, cache size now: " + NiceFileSize(size), Helpers.LogLevel.Debug);
+ }
+ else
+ {
+ Logger.Log("Cache size is " + NiceFileSize(size) + ", file deletion not needed", Helpers.LogLevel.Debug);
+ }
+
+ }
+
+ ///
+ /// Asynchronously brings cache size to the 90% of the max size
+ ///
+ public void BeginPrune()
+ {
+ // Check if the background cache cleaning thread is active first
+ if (cleanerThread != null && cleanerThread.IsAlive)
+ {
+ return;
+ }
+
+ lock (this)
+ {
+ cleanerThread = new Thread(new ThreadStart(this.Prune));
+ cleanerThread.IsBackground = true;
+ cleanerThread.Start();
+ }
+ }
+
+ ///
+ /// Adds up file sizes passes in a FileInfo array
+ ///
+ long GetFileSize(FileInfo[] files)
+ {
+ long ret = 0;
+ foreach (FileInfo file in files)
+ {
+ ret += file.Length;
+ }
+ return ret;
+ }
+
+ ///
+ /// Checks whether caching is enabled
+ ///
+ private bool Operational()
+ {
+ return Client.Settings.USE_TEXTURE_CACHE;
+ }
+
+ ///
+ /// Periodically prune the cache
+ ///
+ private void cleanerTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
+ {
+ BeginPrune();
+ }
+
+ ///
+ /// Nicely formats file sizes
+ ///
+ /// Byte size we want to output
+ /// String with humanly readable file size
+ private string NiceFileSize(long byteCount)
+ {
+ string size = "0 Bytes";
+ if (byteCount >= 1073741824)
+ size = String.Format("{0:##.##}", byteCount / 1073741824) + " GB";
+ else if (byteCount >= 1048576)
+ size = String.Format("{0:##.##}", byteCount / 1048576) + " MB";
+ else if (byteCount >= 1024)
+ size = String.Format("{0:##.##}", byteCount / 1024) + " KB";
+ else if (byteCount > 0 && byteCount < 1024)
+ size = byteCount.ToString() + " Bytes";
+
+ return size;
+ }
+
+ ///
+ /// Helper class for sorting files by their last accessed time
+ ///
+ private class SortFilesByAccesTimeHelper : IComparer
+ {
+ int IComparer.Compare(FileInfo f1, FileInfo f2)
+ {
+ if (f1.LastAccessTime > f2.LastAccessTime)
+ return 1;
+ if (f1.LastAccessTime < f2.LastAccessTime)
+ return -1;
+ else
+ return 0;
+ }
+ }
+ }
+}
diff --git a/OpenMetaverse/TexturePipeline.cs b/OpenMetaverse/TexturePipeline.cs
new file mode 100644
index 00000000..f82b7abc
--- /dev/null
+++ b/OpenMetaverse/TexturePipeline.cs
@@ -0,0 +1,332 @@
+/*
+ * Copyright (c) 2008, openmetaverse.org
+ * All rights reserved.
+ *
+ * - Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Neither the name of the openmetaverse.org nor the names
+ * of its contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace OpenMetaverse
+{
+ ///
+ /// Texture request download handler, allows a configurable number of download slots
+ ///
+ public class TexturePipeline
+ {
+ class TaskInfo
+ {
+ public UUID RequestID;
+ public int RequestNbr;
+ public ImageType Type;
+
+ public TaskInfo(UUID reqID, int reqNbr, ImageType type)
+ {
+ RequestID = reqID;
+ RequestNbr = reqNbr;
+ Type = type;
+ }
+ }
+
+ public delegate void DownloadFinishedCallback(UUID id, bool success);
+ public delegate void DownloadProgressCallback(UUID image, int recieved, int total);
+
+ /// Fired when a texture download completes
+ public event DownloadFinishedCallback OnDownloadFinished;
+ /// Fired when some texture data is received
+ public event DownloadProgressCallback OnDownloadProgress;
+
+ /// For keeping track of active threads available/downloading
+ /// textures
+ public int[] ThreadpoolSlots
+ {
+ get { lock (threadpoolSlots) { return threadpoolSlots; } }
+ set { lock (threadpoolSlots) { threadpoolSlots = value; } }
+ }
+
+ GridClient client;
+ /// Maximum concurrent texture requests
+ int maxTextureRequests;
+ /// Queue for image requests that have not been sent out yet
+ Queue> requestQueue;
+ /// Current texture downloads
+ Dictionary currentRequests;
+ /// Storage for completed texture downloads
+ Dictionary completedDownloads;
+ AutoResetEvent[] resetEvents;
+ int[] threadpoolSlots;
+ Thread downloadMaster;
+ bool running;
+
+ ///
+ /// Default constructor
+ ///
+ /// Reference to SecondLife client
+ /// Maximum number of concurrent texture requests
+ public TexturePipeline(GridClient client, int maxRequests)
+ {
+ running = true;
+ this.client = client;
+ maxTextureRequests = maxRequests;
+
+ requestQueue = new Queue>();
+ currentRequests = new Dictionary(maxTextureRequests);
+ completedDownloads = new Dictionary();
+ resetEvents = new AutoResetEvent[maxTextureRequests];
+ threadpoolSlots = new int[maxTextureRequests];
+
+ // Pre-configure autoreset events/download slots
+ for (int i = 0; i < maxTextureRequests; i++)
+ {
+ resetEvents[i] = new AutoResetEvent(false);
+ threadpoolSlots[i] = -1;
+ }
+
+ client.Assets.OnImageReceived += Assets_OnImageReceived;
+ client.Assets.OnImageReceiveProgress += Assets_OnImageReceiveProgress;
+
+ // Fire up the texture download thread
+ downloadMaster = new Thread(new ThreadStart(DownloadThread));
+ downloadMaster.Start();
+ }
+
+ public void Shutdown()
+ {
+ client.Assets.OnImageReceived -= Assets_OnImageReceived;
+ client.Assets.OnImageReceiveProgress -= Assets_OnImageReceiveProgress;
+
+ requestQueue.Clear();
+
+ for (int i = 0; i < resetEvents.Length; i++)
+ if (resetEvents[i] != null)
+ resetEvents[i].Set();
+
+ running = false;
+ }
+
+ ///
+ /// Request a texture be downloaded, once downloaded OnImageRenderReady event will be fired
+ /// containing texture key which can be used to retrieve texture with GetTextureToRender method
+ ///
+ /// Texture to request
+ /// Type of the requested texture
+ public void RequestTexture(UUID textureID, ImageType type)
+ {
+ if (client.Assets.Cache.HasImage(textureID))
+ {
+ // Add to rendering dictionary
+ lock (completedDownloads)
+ {
+ if (!completedDownloads.ContainsKey(textureID))
+ {
+ completedDownloads.Add(textureID, client.Assets.Cache.GetCachedImage(textureID));
+
+ // Let any subscribers know about it
+ if (OnDownloadFinished != null)
+ OnDownloadFinished(textureID, true);
+ }
+ else
+ {
+ // This image has already been served up, ignore this request
+ }
+ }
+ }
+ else
+ {
+ lock (requestQueue)
+ {
+ // Make sure the request isn't already queued up
+ foreach (KeyValuePair kvp in requestQueue)
+ if (kvp.Key == textureID)
+ return;
+
+ // Make sure we aren't already downloading the texture
+ if (!currentRequests.ContainsKey(textureID))
+ {
+ requestQueue.Enqueue(new KeyValuePair(textureID, type));
+ }
+ }
+ }
+ }
+
+ ///
+ /// retrieve texture information from dictionary
+ ///
+ /// Texture ID
+ /// ImageDownload object
+ public ImageDownload GetTextureToRender(UUID textureID)
+ {
+ ImageDownload renderable = new ImageDownload();
+ lock (completedDownloads)
+ {
+ if (completedDownloads.ContainsKey(textureID))
+ {
+ renderable = completedDownloads[textureID];
+ }
+ else
+ {
+ Logger.Log("Requested texture data for texture that does not exist in dictionary", Helpers.LogLevel.Warning);
+ }
+ return renderable;
+ }
+ }
+
+ ///
+ /// Remove no longer necessary texture from dictionary
+ ///
+ ///
+ public void RemoveFromPipeline(UUID textureID)
+ {
+ lock (completedDownloads)
+ {
+ if (completedDownloads.ContainsKey(textureID))
+ completedDownloads.Remove(textureID);
+ }
+ }
+
+ ///
+ /// Master Download Thread, Queues up downloads in the threadpool
+ ///
+ private void DownloadThread()
+ {
+ int reqNbr;
+
+ while (running)
+ {
+ if (requestQueue.Count > 0)
+ {
+ reqNbr = -1;
+ // find available slot for reset event
+ for (int i = 0; i < threadpoolSlots.Length; i++)
+ {
+ if (threadpoolSlots[i] == -1)
+ {
+ threadpoolSlots[i] = 1;
+ reqNbr = i;
+ break;
+ }
+ }
+
+ if (reqNbr != -1)
+ {
+ KeyValuePair request;
+ lock (requestQueue)
+ request = requestQueue.Dequeue();
+
+ Logger.DebugLog(String.Format("Sending Worker thread new download request {0}", reqNbr));
+ ThreadPool.QueueUserWorkItem(new WaitCallback(TextureRequestDoWork), new TaskInfo(request.Key, reqNbr, request.Value));
+
+ continue;
+ }
+ }
+
+ // Queue was empty, let's give up some CPU time
+ Thread.Sleep(500);
+ }
+
+ Logger.Log("Texture pipeline shutting down", Helpers.LogLevel.Info);
+ }
+
+ private void TextureRequestDoWork(Object threadContext)
+ {
+ TaskInfo ti = (TaskInfo)threadContext;
+
+ lock (currentRequests)
+ {
+ if (currentRequests.ContainsKey(ti.RequestID))
+ {
+ threadpoolSlots[ti.RequestNbr] = -1;
+ return;
+ }
+ else
+ {
+ currentRequests.Add(ti.RequestID, ti.RequestNbr);
+ }
+ }
+
+ Logger.DebugLog(String.Format("Worker {0} Requesting {1}", ti.RequestNbr, ti.RequestID));
+
+ resetEvents[ti.RequestNbr].Reset();
+ client.Assets.RequestImage(ti.RequestID, ti.Type);
+
+ // don't release this worker slot until texture is downloaded or timeout occurs
+ if (!resetEvents[ti.RequestNbr].WaitOne(30 * 1000, false))
+ {
+ // Timed out
+ Logger.Log("Worker " + ti.RequestNbr + " Timeout waiting for Texture " + ti.RequestID + " to Download", Helpers.LogLevel.Warning);
+
+ lock (currentRequests)
+ currentRequests.Remove(ti.RequestID);
+
+ if (OnDownloadFinished != null)
+ OnDownloadFinished(ti.RequestID, false);
+ }
+
+ // free up this download slot
+ threadpoolSlots[ti.RequestNbr] = -1;
+ }
+
+ private void Assets_OnImageReceived(ImageDownload image, AssetTexture asset)
+ {
+ int requestNbr;
+ bool found;
+
+ lock (currentRequests)
+ found = currentRequests.TryGetValue(image.ID, out requestNbr);
+
+ if (asset != null && found)
+ {
+ Logger.DebugLog(String.Format("Worker {0} Downloaded texture {1}", requestNbr, image.ID));
+
+ // Free up this slot in the ThreadPool
+ lock (currentRequests)
+ currentRequests.Remove(image.ID);
+
+ resetEvents[requestNbr].Set();
+
+ if (image.Success)
+ {
+ // Add to the completed texture dictionary
+ lock (completedDownloads)
+ completedDownloads[image.ID] = image;
+ }
+ else
+ {
+ Logger.Log(String.Format("Download of texture {0} failed. NotFound={1}", image.ID, image.NotFound),
+ Helpers.LogLevel.Warning);
+ }
+
+ // Let any subscribers know about it
+ if (OnDownloadFinished != null)
+ OnDownloadFinished(image.ID, image.Success);
+ }
+ }
+
+ private void Assets_OnImageReceiveProgress(UUID image, int lastPacket, int recieved, int total)
+ {
+ if (OnDownloadProgress != null && currentRequests.ContainsKey(image))
+ OnDownloadProgress(image, recieved, total);
+ }
+ }
+}
diff --git a/Programs/PrimWorkshop/TexturePipeline.cs b/Programs/PrimWorkshop/TexturePipeline.cs
index d1987694..08d613c0 100644
--- a/Programs/PrimWorkshop/TexturePipeline.cs
+++ b/Programs/PrimWorkshop/TexturePipeline.cs
@@ -23,321 +23,5 @@ using OpenMetaverse;
*/
namespace PrimWorkshop
{
- class TaskInfo
- {
- public UUID RequestID;
- public int RequestNbr;
-
-
- public TaskInfo(UUID reqID, int reqNbr)
- {
- RequestID = reqID;
- RequestNbr = reqNbr;
- }
- }
-
- ///
- /// Texture request download handler, allows a configurable number of download slots
- ///
- public class TexturePipeline
- {
- private static GridClient Client;
-
- // queue for requested images
- private Queue RequestQueue;
-
- // list of current requests in process
- private Dictionary CurrentRequests;
-
- private static AutoResetEvent[] resetEvents;
-
- private static int[] threadpoolSlots;
-
- ///
- /// For keeping track of active threads available/downloading textures
- ///
- public static int[] ThreadpoolSlots
- {
- get { lock (threadpoolSlots) { return threadpoolSlots; }}
- set { lock (threadpoolSlots) { threadpoolSlots = value; } }
- }
-
- // storage for images ready to render
- private Dictionary RenderReady;
-
- // maximum allowed concurrent requests at once
- const int MAX_TEXTURE_REQUESTS = 10;
-
- ///
- ///
- ///
- ///
- ///
- public delegate void DownloadFinishedCallback(UUID id, bool success);
- ///
- ///
- ///
- ///
- ///
- ///
- public delegate void DownloadProgressCallback(UUID image, int recieved, int total);
-
- /// Fired when a texture download completes
- public event DownloadFinishedCallback OnDownloadFinished;
- ///
- public event DownloadProgressCallback OnDownloadProgress;
-
- private Thread downloadMaster;
- private bool Running;
-
- private AssetManager.ImageReceivedCallback DownloadCallback;
- private AssetManager.ImageReceiveProgressCallback DownloadProgCallback;
-
- ///
- /// Default constructor
- ///
- /// Reference to SecondLife client
- public TexturePipeline(GridClient client)
- {
- Running = true;
-
- RequestQueue = new Queue();
- CurrentRequests = new Dictionary(MAX_TEXTURE_REQUESTS);
-
- RenderReady = new Dictionary();
-
- resetEvents = new AutoResetEvent[MAX_TEXTURE_REQUESTS];
- threadpoolSlots = new int[MAX_TEXTURE_REQUESTS];
-
- // pre-configure autoreset events/download slots
- for (int i = 0; i < MAX_TEXTURE_REQUESTS; i++)
- {
- resetEvents[i] = new AutoResetEvent(false);
- threadpoolSlots[i] = -1;
- }
-
- Client = client;
-
- DownloadCallback = new AssetManager.ImageReceivedCallback(Assets_OnImageReceived);
- DownloadProgCallback = new AssetManager.ImageReceiveProgressCallback(Assets_OnImageReceiveProgress);
- Client.Assets.OnImageReceived += DownloadCallback;
- Client.Assets.OnImageReceiveProgress += DownloadProgCallback;
-
- // Fire up the texture download thread
- downloadMaster = new Thread(new ThreadStart(DownloadThread));
- downloadMaster.Start();
- }
-
- public void Shutdown()
- {
- Client.Assets.OnImageReceived -= DownloadCallback;
- Client.Assets.OnImageReceiveProgress -= DownloadProgCallback;
-
- RequestQueue.Clear();
-
- for (int i = 0; i < resetEvents.Length; i++)
- if (resetEvents[i] != null)
- resetEvents[i].Set();
-
- Running = false;
- }
-
- ///
- /// Request a texture be downloaded, once downloaded OnImageRenderReady event will be fired
- /// containing texture key which can be used to retrieve texture with GetTextureToRender method
- ///
- /// id of Texture to request
- public void RequestTexture(UUID textureID)
- {
- if (Client.Assets.Cache.HasImage(textureID))
- {
- // Add to rendering dictionary
- lock (RenderReady)
- {
- if (!RenderReady.ContainsKey(textureID))
- {
- RenderReady.Add(textureID, Client.Assets.Cache.GetCachedImage(textureID));
-
- // Let any subscribers know about it
- if (OnDownloadFinished != null)
- {
- OnDownloadFinished(textureID, true);
- }
- }
- else
- {
- // This image has already been served up, ignore this request
- }
- }
- }
- else
- {
- lock (RequestQueue)
- {
- // Make sure we aren't already downloading the texture
- if (!RequestQueue.Contains(textureID) && !CurrentRequests.ContainsKey(textureID))
- {
- RequestQueue.Enqueue(textureID);
- }
- }
- }
- }
-
- ///
- /// retrieve texture information from dictionary
- ///
- /// Texture ID
- /// ImageDownload object
- public ImageDownload GetTextureToRender(UUID textureID)
- {
- ImageDownload renderable = new ImageDownload();
- lock (RenderReady)
- {
- if (RenderReady.ContainsKey(textureID))
- {
- renderable = RenderReady[textureID];
- }
- else
- {
- Logger.Log("Requested texture data for texture that does not exist in dictionary", Helpers.LogLevel.Warning);
- }
- return renderable;
- }
- }
-
- ///
- /// Remove no longer necessary texture from dictionary
- ///
- ///
- public void RemoveFromPipeline(UUID textureID)
- {
- lock (RenderReady)
- {
- if (RenderReady.ContainsKey(textureID))
- RenderReady.Remove(textureID);
- }
- }
-
- ///
- /// Master Download Thread, Queues up downloads in the threadpool
- ///
- private void DownloadThread()
- {
- int reqNbr;
-
- while (Running)
- {
- if (RequestQueue.Count > 0)
- {
- reqNbr = -1;
- // find available slot for reset event
- for (int i = 0; i < threadpoolSlots.Length; i++)
- {
- if (threadpoolSlots[i] == -1)
- {
- threadpoolSlots[i] = 1;
- reqNbr = i;
- break;
- }
- }
-
- if (reqNbr != -1)
- {
- UUID requestID;
- lock (RequestQueue)
- requestID = RequestQueue.Dequeue();
-
- Logger.DebugLog(String.Format("Sending Worker thread new download request {0}", reqNbr));
- ThreadPool.QueueUserWorkItem(new WaitCallback(textureRequestDoWork), new TaskInfo(requestID, reqNbr));
-
- continue;
- }
- }
-
- // Queue was empty, let's give up some CPU time
- Thread.Sleep(500);
- }
- }
-
- void textureRequestDoWork(Object threadContext)
- {
- TaskInfo ti = (TaskInfo)threadContext;
-
- lock (CurrentRequests)
- {
- if (CurrentRequests.ContainsKey(ti.RequestID))
- {
- threadpoolSlots[ti.RequestNbr] = -1;
- return;
- }
- else
- {
- CurrentRequests.Add(ti.RequestID, ti.RequestNbr);
- }
- }
-
- Logger.DebugLog(String.Format("Worker {0} Requesting {1}", ti.RequestNbr, ti.RequestID));
-
- resetEvents[ti.RequestNbr].Reset();
- Client.Assets.RequestImage(ti.RequestID, ImageType.Normal);
-
- // don't release this worker slot until texture is downloaded or timeout occurs
- if (!resetEvents[ti.RequestNbr].WaitOne(30 * 1000, false))
- {
- // Timed out
- Logger.Log("Worker " + ti.RequestNbr + " Timeout waiting for Texture " + ti.RequestID + " to Download", Helpers.LogLevel.Warning);
-
- lock (CurrentRequests)
- CurrentRequests.Remove(ti.RequestID);
- }
-
- // free up this download slot
- threadpoolSlots[ti.RequestNbr] = -1;
- }
-
- private void Assets_OnImageReceived(ImageDownload image, AssetTexture asset)
- {
- // Free up this slot in the ThreadPool
- lock (CurrentRequests)
- {
- int requestNbr;
- if (asset != null && CurrentRequests.TryGetValue(image.ID, out requestNbr))
- {
- Logger.DebugLog(String.Format("Worker {0} Downloaded texture {1}", requestNbr, image.ID));
- resetEvents[requestNbr].Set();
- CurrentRequests.Remove(image.ID);
- }
- }
-
- if (image.Success)
- {
- lock (RenderReady)
- {
- if (!RenderReady.ContainsKey(image.ID))
- {
- // Add to rendering dictionary
- RenderReady.Add(image.ID, image);
- }
- }
- }
- else
- {
- Console.WriteLine(String.Format("Download of texture {0} failed. NotFound={1}", image.ID, image.NotFound));
- }
-
- // Let any subscribers know about it
- if (OnDownloadFinished != null)
- {
- OnDownloadFinished(image.ID, image.Success);
- }
- }
-
- private void Assets_OnImageReceiveProgress(UUID image, int lastPacket, int recieved, int total)
- {
- if (OnDownloadProgress != null)
- {
- OnDownloadProgress(image, recieved, total);
- }
- }
- }
+
}
diff --git a/Programs/PrimWorkshop/frmBrowser.cs b/Programs/PrimWorkshop/frmBrowser.cs
index 65717a22..d6569d9c 100644
--- a/Programs/PrimWorkshop/frmBrowser.cs
+++ b/Programs/PrimWorkshop/frmBrowser.cs
@@ -170,7 +170,7 @@ namespace PrimWorkshop
// Initialize the texture download pipeline
if (TextureDownloader != null)
TextureDownloader.Shutdown();
- TextureDownloader = new TexturePipeline(Client);
+ TextureDownloader = new TexturePipeline(Client, 10);
TextureDownloader.OnDownloadFinished += new TexturePipeline.DownloadFinishedCallback(TextureDownloader_OnDownloadFinished);
TextureDownloader.OnDownloadProgress += new TexturePipeline.DownloadProgressCallback(TextureDownloader_OnDownloadProgress);
@@ -970,7 +970,7 @@ namespace PrimWorkshop
if (!Textures.ContainsKey(teFace.TextureID))
{
// We haven't constructed this image in OpenGL yet, get ahold of it
- TextureDownloader.RequestTexture(teFace.TextureID);
+ TextureDownloader.RequestTexture(teFace.TextureID, ImageType.Normal);
}
}
}
diff --git a/Programs/Simian/Extensions/AvatarManager.cs b/Programs/Simian/Extensions/AvatarManager.cs
index 43553b73..1bc889f9 100644
--- a/Programs/Simian/Extensions/AvatarManager.cs
+++ b/Programs/Simian/Extensions/AvatarManager.cs
@@ -62,6 +62,12 @@ namespace Simian.Extensions
return agent.Animations.Remove(animID);
}
+ public bool ClearAnimations(Agent agent)
+ {
+ agent.Animations.Clear();
+ return true;
+ }
+
public void SendAnimations(Agent agent)
{
AvatarAnimationPacket sendAnim = new AvatarAnimationPacket();
@@ -104,7 +110,7 @@ namespace Simian.Extensions
// Remove the avatar from the scene
SimulationObject obj;
if (server.Scene.TryGetObject(agent.AgentID, out obj))
- server.Scene.ObjectRemove(this, obj);
+ server.Scene.ObjectRemove(this, obj.Prim.LocalID);
else
Logger.Log("Disconnecting an agent that is not in the scene", Helpers.LogLevel.Warning);
diff --git a/Programs/Simian/Extensions/ImageDelivery.cs b/Programs/Simian/Extensions/ImageDelivery.cs
index 4a6221b9..dd0ecea9 100644
--- a/Programs/Simian/Extensions/ImageDelivery.cs
+++ b/Programs/Simian/Extensions/ImageDelivery.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Threading;
using ExtensionLoader;
using OpenMetaverse;
-using OpenMetaverse.Imaging;
using OpenMetaverse.Packets;
namespace Simian.Extensions
@@ -14,14 +13,16 @@ namespace Simian.Extensions
public const int IMAGE_PACKET_SIZE = 1000;
public AssetTexture Texture;
+ public Agent Agent;
public int DiscardLevel;
public float Priority;
public int CurrentPacket;
public int StopPacket;
- public ImageDownload(AssetTexture texture, int discardLevel, float priority, int packet)
+ public ImageDownload(AssetTexture texture, Agent agent, int discardLevel, float priority, int packet)
{
Texture = texture;
+ Agent = agent;
Update(discardLevel, priority, packet);
}
@@ -35,9 +36,28 @@ namespace Simian.Extensions
public void Update(int discardLevel, float priority, int packet)
{
Priority = priority;
- DiscardLevel = Utils.Clamp(discardLevel, 0, Texture.LayerInfo.Length - 1);
- StopPacket = GetPacketForBytePosition(Texture.LayerInfo[(Texture.LayerInfo.Length - 1) - DiscardLevel].End);
- CurrentPacket = Utils.Clamp(packet, 1, TexturePacketCount());
+
+ if (Texture != null)
+ {
+ if (Texture.LayerInfo != null && Texture.LayerInfo.Length > 0)
+ {
+ DiscardLevel = Utils.Clamp(discardLevel, 0, Texture.LayerInfo.Length - 1);
+ StopPacket = GetPacketForBytePosition(Texture.LayerInfo[(Texture.LayerInfo.Length - 1) - DiscardLevel].End);
+ }
+ else
+ {
+ DiscardLevel = 0;
+ StopPacket = GetPacketForBytePosition(Texture.AssetData.Length);
+ }
+
+ CurrentPacket = Utils.Clamp(packet, 1, TexturePacketCount());
+ }
+ else
+ {
+ DiscardLevel = discardLevel;
+ Priority = priority;
+ CurrentPacket = packet;
+ }
}
///
@@ -94,7 +114,7 @@ namespace Simian.Extensions
{
this.server = server;
- server.UDP.RegisterPacketCallback(PacketType.RequestImage, new PacketCallback(RequestImageHandler));
+ server.UDP.RegisterPacketCallback(PacketType.RequestImage, RequestImageHandler);
}
public void Stop()
@@ -151,91 +171,7 @@ namespace Simian.Extensions
Asset asset;
if (server.Assets.TryGetAsset(block.Image, out asset) && asset is AssetTexture)
{
- download = new ImageDownload((AssetTexture)asset, block.DiscardLevel, block.DownloadPriority,
- (int)block.Packet);
-
- Logger.DebugLog(String.Format(
- "Starting new download for {0}, DiscardLevel: {1}, Priority: {2}, Start: {3}, End: {4}, Total: {5}",
- block.Image, block.DiscardLevel, block.DownloadPriority, download.CurrentPacket, download.StopPacket,
- download.TexturePacketCount()));
-
- // Send initial data
- ImageDataPacket data = new ImageDataPacket();
- data.ImageID.Codec = (byte)ImageCodec.J2C;
- data.ImageID.ID = download.Texture.AssetID;
- data.ImageID.Packets = (ushort)download.TexturePacketCount();
- data.ImageID.Size = (uint)download.Texture.AssetData.Length;
-
- // The first bytes of the image are always sent in the ImageData packet
- data.ImageData = new ImageDataPacket.ImageDataBlock();
- int imageDataSize = (download.Texture.AssetData.Length >= ImageDownload.FIRST_IMAGE_PACKET_SIZE) ?
- ImageDownload.FIRST_IMAGE_PACKET_SIZE : download.Texture.AssetData.Length;
- try
- {
- data.ImageData.Data = new byte[imageDataSize];
- Buffer.BlockCopy(download.Texture.AssetData, 0, data.ImageData.Data, 0, imageDataSize);
- }
- catch (Exception ex)
- {
- Logger.Log(String.Format("{0}: imageDataSize={1}", ex.Message, imageDataSize),
- Helpers.LogLevel.Error);
- }
-
- server.UDP.SendPacket(agent.AgentID, data, PacketCategory.Texture);
-
- // Check if ImagePacket packets need to be sent to complete this transfer
- if (download.CurrentPacket <= download.StopPacket)
- {
- // Insert this download into the dictionary
- lock (CurrentDownloads)
- CurrentDownloads[block.Image] = download;
-
- // Send all of the remaining packets
- ThreadPool.QueueUserWorkItem(
- delegate(object obj)
- {
- while (download.CurrentPacket <= download.StopPacket)
- {
- if (download.Priority == 0.0f && download.DiscardLevel == -1)
- break;
-
- lock (download)
- {
- int imagePacketSize = (download.CurrentPacket == download.TexturePacketCount() - 1) ?
- download.LastPacketSize() : ImageDownload.IMAGE_PACKET_SIZE;
-
- ImagePacketPacket transfer = new ImagePacketPacket();
- transfer.ImageID.ID = block.Image;
- transfer.ImageID.Packet = (ushort)download.CurrentPacket;
- transfer.ImageData.Data = new byte[imagePacketSize];
-
- try
- {
- Buffer.BlockCopy(download.Texture.AssetData, download.CurrentBytePosition(),
- transfer.ImageData.Data, 0, imagePacketSize);
- }
- catch (Exception ex)
- {
- Logger.Log(String.Format(
- "{0}: CurrentBytePosition()={1}, AssetData.Length={2} imagePacketSize={3}",
- ex.Message, download.CurrentBytePosition(), download.Texture.AssetData.Length,
- imagePacketSize), Helpers.LogLevel.Error);
- }
-
- server.UDP.SendPacket(agent.AgentID, transfer, PacketCategory.Texture);
-
- ++download.CurrentPacket;
- }
- }
-
- Logger.DebugLog("Completed image transfer for " + block.Image.ToString());
-
- // Transfer is complete, remove the reference
- lock (CurrentDownloads)
- CurrentDownloads.Remove(block.Image);
- }
- );
- }
+ SendTexture(agent, (AssetTexture)asset, block.DiscardLevel, (int)block.Packet, block.DownloadPriority);
}
else
{
@@ -248,5 +184,93 @@ namespace Simian.Extensions
}
}
}
+
+ void SendTexture(Agent agent, AssetTexture texture, int discardLevel, int packet, float priority)
+ {
+ ImageDownload download = new ImageDownload(texture, agent, discardLevel, priority, packet);
+
+ Logger.DebugLog(String.Format(
+ "Starting new download for {0}, DiscardLevel: {1}, Priority: {2}, Start: {3}, End: {4}, Total: {5}",
+ texture.AssetID, discardLevel, priority, download.CurrentPacket, download.StopPacket,
+ download.TexturePacketCount()));
+
+ // Send initial data
+ ImageDataPacket data = new ImageDataPacket();
+ data.ImageID.Codec = (byte)ImageCodec.J2C;
+ data.ImageID.ID = download.Texture.AssetID;
+ data.ImageID.Packets = (ushort)download.TexturePacketCount();
+ data.ImageID.Size = (uint)download.Texture.AssetData.Length;
+
+ // The first bytes of the image are always sent in the ImageData packet
+ data.ImageData = new ImageDataPacket.ImageDataBlock();
+ int imageDataSize = (download.Texture.AssetData.Length >= ImageDownload.FIRST_IMAGE_PACKET_SIZE) ?
+ ImageDownload.FIRST_IMAGE_PACKET_SIZE : download.Texture.AssetData.Length;
+ try
+ {
+ data.ImageData.Data = new byte[imageDataSize];
+ Buffer.BlockCopy(download.Texture.AssetData, 0, data.ImageData.Data, 0, imageDataSize);
+ }
+ catch (Exception ex)
+ {
+ Logger.Log(String.Format("{0}: imageDataSize={1}", ex.Message, imageDataSize),
+ Helpers.LogLevel.Error);
+ }
+
+ server.UDP.SendPacket(agent.AgentID, data, PacketCategory.Texture);
+
+ // Check if ImagePacket packets need to be sent to complete this transfer
+ if (download.CurrentPacket <= download.StopPacket)
+ {
+ // Insert this download into the dictionary
+ lock (CurrentDownloads)
+ CurrentDownloads[texture.AssetID] = download;
+
+ // Send all of the remaining packets
+ ThreadPool.QueueUserWorkItem(
+ delegate(object obj)
+ {
+ while (download.CurrentPacket <= download.StopPacket)
+ {
+ if (download.Priority == 0.0f && download.DiscardLevel == -1)
+ break;
+
+ lock (download)
+ {
+ int imagePacketSize = (download.CurrentPacket == download.TexturePacketCount() - 1) ?
+ download.LastPacketSize() : ImageDownload.IMAGE_PACKET_SIZE;
+
+ ImagePacketPacket transfer = new ImagePacketPacket();
+ transfer.ImageID.ID = texture.AssetID;
+ transfer.ImageID.Packet = (ushort)download.CurrentPacket;
+ transfer.ImageData.Data = new byte[imagePacketSize];
+
+ try
+ {
+ Buffer.BlockCopy(download.Texture.AssetData, download.CurrentBytePosition(),
+ transfer.ImageData.Data, 0, imagePacketSize);
+ }
+ catch (Exception ex)
+ {
+ Logger.Log(String.Format(
+ "{0}: CurrentBytePosition()={1}, AssetData.Length={2} imagePacketSize={3}",
+ ex.Message, download.CurrentBytePosition(), download.Texture.AssetData.Length,
+ imagePacketSize), Helpers.LogLevel.Error);
+ }
+
+ server.UDP.SendPacket(agent.AgentID, transfer, PacketCategory.Texture);
+
+ ++download.CurrentPacket;
+ }
+ }
+
+ Logger.DebugLog("Completed image transfer for " + texture.AssetID.ToString());
+
+ // Transfer is complete, remove the reference
+ lock (CurrentDownloads)
+ CurrentDownloads.Remove(texture.AssetID);
+ }
+ );
+ }
+ }
}
}
diff --git a/Programs/Simian/Extensions/Movement.cs b/Programs/Simian/Extensions/Movement.cs
index 038cd3a5..24b10dc5 100644
--- a/Programs/Simian/Extensions/Movement.cs
+++ b/Programs/Simian/Extensions/Movement.cs
@@ -310,7 +310,6 @@ namespace Simian.Extensions
if (animsChanged)
server.Avatars.SendAnimations(agent);
-
float maxVel = AVATAR_TERMINAL_VELOCITY * seconds;
// static acceleration when any control is held, otherwise none
@@ -339,7 +338,6 @@ namespace Simian.Extensions
else if (agent.Avatar.Position.Y > 255) agent.Avatar.Position.Y = 255f;
if (agent.Avatar.Position.Z < lowerLimit) agent.Avatar.Position.Z = lowerLimit;
-
}
}
}
@@ -403,6 +401,5 @@ namespace Simian.Extensions
//Logger.Log(String.Format("Agent wants to set height={0}, width={1}",
// heightWidth.HeightWidthBlock.Height, heightWidth.HeightWidthBlock.Width), Helpers.LogLevel.Info);
}
-
}
}
diff --git a/Programs/Simian/Extensions/ObjectManager.cs b/Programs/Simian/Extensions/ObjectManager.cs
index a60ff072..4cd369b1 100644
--- a/Programs/Simian/Extensions/ObjectManager.cs
+++ b/Programs/Simian/Extensions/ObjectManager.cs
@@ -440,7 +440,7 @@ namespace Simian.Extensions
data.ProfileEnd = Primitive.UnpackEndCut(block.ProfileEnd);
data.ProfileHollow = Primitive.UnpackProfileHollow(block.ProfileHollow);
- server.Scene.ObjectModify(this, obj, data);
+ server.Scene.ObjectModify(this, obj.Prim.LocalID, data);
}
else
{
@@ -569,7 +569,7 @@ namespace Simian.Extensions
server.Inventory.CreateItem(agent.AgentID, obj.Prim.Properties.Name, obj.Prim.Properties.Description, InventoryType.Object,
AssetType.Object, obj.Prim.ID, trash.ID, PermissionMask.All, PermissionMask.All, agent.AgentID,
obj.Prim.Properties.CreatorID, derez.AgentBlock.TransactionID, 0, true);
- server.Scene.ObjectRemove(this, obj);
+ server.Scene.ObjectRemove(this, obj.Prim.LocalID);
Logger.DebugLog(String.Format("Derezzed prim {0} to agent inventory trash", obj.Prim.LocalID));
}
@@ -606,6 +606,7 @@ namespace Simian.Extensions
for (int i = 0; i < update.ObjectData.Length; i++)
{
+ bool scaled = false;
MultipleObjectUpdatePacket.ObjectDataBlock block = update.ObjectData[i];
SimulationObject obj;
@@ -630,6 +631,7 @@ namespace Simian.Extensions
}
if ((type & UpdateType.Scale) != 0)
{
+ scaled = true;
scale = new Vector3(block.Data, pos);
pos += 12;
@@ -637,12 +639,19 @@ namespace Simian.Extensions
bool uniform = ((type & UpdateType.Uniform) != 0);
}
- // Although the object has already been modified, we need
- // to inform the scene manager of the changes so they are
- // sent to clients and propagated to other extensions
- server.Scene.ObjectTransform(this, obj, position, rotation,
- obj.Prim.Velocity, obj.Prim.Acceleration, obj.Prim.AngularVelocity,
- scale);
+ if (scaled)
+ {
+ obj.Prim.Position = position;
+ obj.Prim.Rotation = rotation;
+ obj.Prim.Scale = scale;
+
+ server.Scene.ObjectAdd(this, obj, PrimFlags.None);
+ }
+ else
+ {
+ server.Scene.ObjectTransform(this, obj.Prim.LocalID, position, rotation,
+ obj.Prim.Velocity, obj.Prim.Acceleration, obj.Prim.AngularVelocity);
+ }
}
else
{
diff --git a/Programs/Simian/Extensions/Periscope.cs b/Programs/Simian/Extensions/Periscope.cs
index e680f06a..2300cec9 100644
--- a/Programs/Simian/Extensions/Periscope.cs
+++ b/Programs/Simian/Extensions/Periscope.cs
@@ -12,45 +12,54 @@ namespace Simian.Extensions
const string FIRST_NAME = "Testing";
const string LAST_NAME = "Anvil";
const string PASSWORD = "testinganvil";
+ const string SIMULATOR = "Svarga";
+
+ public Agent MasterAgent = null;
Simian server;
GridClient client;
+ PeriscopeImageDelivery imageDelivery;
+ PeriscopeMovement movement;
+ object loginLock = new object();
public Periscope()
{
- client = new GridClient();
- client.Settings.SEND_AGENT_UPDATES = false;
-
- client.Objects.OnNewPrim += new OpenMetaverse.ObjectManager.NewPrimCallback(Objects_OnNewPrim);
- client.Terrain.OnLandPatch += new TerrainManager.LandPatchCallback(Terrain_OnLandPatch);
}
public void Start(Simian server)
{
this.server = server;
+
+ client = new GridClient();
+ Settings.LOG_LEVEL = Helpers.LogLevel.Info;
+ client.Settings.MULTIPLE_SIMS = false;
+ client.Settings.SEND_AGENT_UPDATES = false;
+
+ client.Network.OnCurrentSimChanged += new NetworkManager.CurrentSimChangedCallback(Network_OnCurrentSimChanged);
+ client.Objects.OnNewPrim += new OpenMetaverse.ObjectManager.NewPrimCallback(Objects_OnNewPrim);
+ client.Objects.OnNewFoliage += new OpenMetaverse.ObjectManager.NewFoliageCallback(Objects_OnNewFoliage);
+ client.Objects.OnNewAvatar += new OpenMetaverse.ObjectManager.NewAvatarCallback(Objects_OnNewAvatar);
+ client.Objects.OnNewAttachment += new OpenMetaverse.ObjectManager.NewAttachmentCallback(Objects_OnNewAttachment);
+ client.Objects.OnObjectKilled += new OpenMetaverse.ObjectManager.KillObjectCallback(Objects_OnObjectKilled);
+ client.Objects.OnObjectUpdated += new OpenMetaverse.ObjectManager.ObjectUpdatedCallback(Objects_OnObjectUpdated);
+ client.Avatars.OnAvatarAppearance += new OpenMetaverse.AvatarManager.AvatarAppearanceCallback(Avatars_OnAvatarAppearance);
+ client.Terrain.OnLandPatch += new TerrainManager.LandPatchCallback(Terrain_OnLandPatch);
+ client.Self.OnChat += new AgentManager.ChatCallback(Self_OnChat);
+ client.Network.RegisterCallback(PacketType.AvatarAnimation, AvatarAnimationHandler);
+ client.Network.RegisterCallback(PacketType.RegionHandshake, RegionHandshakeHandler);
+
server.UDP.RegisterPacketCallback(PacketType.AgentUpdate, AgentUpdateHandler);
+ server.UDP.RegisterPacketCallback(PacketType.ChatFromViewer, ChatFromViewerHandler);
- // Start the login process
- Thread loginThread = new Thread(new ThreadStart(
- delegate()
- {
- client.Network.Login(FIRST_NAME, LAST_NAME, PASSWORD, "Simian Periscope", "1.0.0");
-
- if (client.Network.Connected)
- {
- Logger.Log("Periscope is connected: " + client.Network.LoginMessage, Helpers.LogLevel.Info);
- }
- else
- {
- Logger.Log("Periscope failed to connect to the foreign grid: " + client.Network.LoginErrorKey, Helpers.LogLevel.Error);
- }
- }
- ));
- loginThread.Start();
+ imageDelivery = new PeriscopeImageDelivery(server, client);
+ movement = new PeriscopeMovement(server, this);
}
public void Stop()
{
+ movement.Stop();
+ imageDelivery.Stop();
+
if (client.Network.Connected)
client.Network.Logout();
}
@@ -58,22 +67,211 @@ namespace Simian.Extensions
void Objects_OnNewPrim(Simulator simulator, Primitive prim, ulong regionHandle, ushort timeDilation)
{
SimulationObject simObj = new SimulationObject(prim, server);
- server.Scene.ObjectAdd(this, simObj, prim.Flags);
+ server.Scene.ObjectAdd(this, simObj, PrimFlags.None);
+ }
+
+ void Objects_OnNewAttachment(Simulator simulator, Primitive prim, ulong regionHandle, ushort timeDilation)
+ {
+ SimulationObject simObj = new SimulationObject(prim, server);
+ server.Scene.ObjectAdd(this, simObj, PrimFlags.None);
+ }
+
+ void Objects_OnNewAvatar(Simulator simulator, Avatar avatar, ulong regionHandle, ushort timeDilation)
+ {
+ // Add the avatar to both the agents list and the scene objects
+ Agent agent = new Agent();
+ agent.AgentID = avatar.ID;
+ agent.Avatar = avatar;
+ agent.CurrentRegionHandle = server.RegionHandle;
+ agent.FirstName = avatar.FirstName;
+ agent.LastName = avatar.LastName;
+
+ lock (server.Agents)
+ server.Agents[agent.AgentID] = agent;
+
+ SimulationObject simObj = new SimulationObject(avatar, server);
+ server.Scene.ObjectAdd(this, simObj, avatar.Flags);
+ }
+
+ void Objects_OnNewFoliage(Simulator simulator, Primitive foliage, ulong regionHandle, ushort timeDilation)
+ {
+ SimulationObject simObj = new SimulationObject(foliage, server);
+ server.Scene.ObjectAdd(this, simObj, foliage.Flags);
+ }
+
+ void Objects_OnObjectUpdated(Simulator simulator, ObjectUpdate update, ulong regionHandle, ushort timeDilation)
+ {
+ server.Scene.ObjectTransform(this, update.LocalID, update.Position, update.Rotation, update.Velocity,
+ update.Acceleration, update.AngularVelocity);
+
+ if (update.LocalID == client.Self.LocalID)
+ {
+ MasterAgent.Avatar.Acceleration = update.Acceleration;
+ MasterAgent.Avatar.AngularVelocity = update.AngularVelocity;
+ MasterAgent.Avatar.CollisionPlane = update.CollisionPlane;
+ MasterAgent.Avatar.Position = update.Position;
+ MasterAgent.Avatar.Rotation = update.Rotation;
+ MasterAgent.Avatar.Velocity = update.Velocity;
+
+ if (update.Textures != null)
+ MasterAgent.Avatar.Textures = update.Textures;
+ }
+ }
+
+ void Objects_OnObjectKilled(Simulator simulator, uint objectID)
+ {
+ server.Scene.ObjectRemove(this, objectID);
+ }
+
+ void Avatars_OnAvatarAppearance(UUID avatarID, bool isTrial, Primitive.TextureEntryFace defaultTexture,
+ Primitive.TextureEntryFace[] faceTextures, List visualParams)
+ {
+ Agent agent;
+ if (server.Agents.TryGetValue(avatarID, out agent))
+ {
+ Primitive.TextureEntry te = new Primitive.TextureEntry(defaultTexture);
+ te.FaceTextures = faceTextures;
+
+ byte[] vp = (visualParams != null && visualParams.Count > 1 ? visualParams.ToArray() : null);
+
+ Logger.Log("[Periscope] Updating foreign avatar appearance for " + avatarID.ToString(), Helpers.LogLevel.Info);
+
+
+ server.Scene.AvatarAppearance(this, agent, te, vp);
+ }
+ else
+ {
+ Logger.Log("[Periscope] Received a foreign avatar appearance for an unknown avatar", Helpers.LogLevel.Warning);
+ }
}
void Terrain_OnLandPatch(Simulator simulator, int x, int y, int width, float[] data)
{
- //throw new NotImplementedException();
+ // TODO: When Simian gets a terrain editing interface, switch this over to
+ // edit the scene heightmap instead of sending packets direct to clients
+ int[] patches = new int[1];
+ patches[0] = (y * 16) + x;
+ LayerDataPacket layer = TerrainCompressor.CreateLandPacket(data, x, y);
+ server.UDP.BroadcastPacket(layer, PacketCategory.Terrain);
+ }
+
+ void Self_OnChat(string message, ChatAudibleLevel audible, ChatType type, ChatSourceType sourceType,
+ string fromName, UUID id, UUID ownerid, Vector3 position)
+ {
+ // TODO: Inject chat into the Scene instead of relaying it
+ ChatFromSimulatorPacket chat = new ChatFromSimulatorPacket();
+ chat.ChatData.Audible = (byte)ChatAudibleLevel.Fully;
+ chat.ChatData.ChatType = (byte)type;
+ chat.ChatData.OwnerID = ownerid;
+ chat.ChatData.SourceID = id;
+ chat.ChatData.SourceType = (byte)sourceType;
+ chat.ChatData.Position = position;
+ chat.ChatData.FromName = Utils.StringToBytes(fromName);
+ chat.ChatData.Message = Utils.StringToBytes(message);
+
+ server.UDP.BroadcastPacket(chat, PacketCategory.Transaction);
+ }
+
+ void Network_OnCurrentSimChanged(Simulator PreviousSimulator)
+ {
+ Logger.Log("[Periscope] Sending bot appearance", Helpers.LogLevel.Info);
+ client.Appearance.SetPreviousAppearance(false);
+ }
+
+ void AvatarAnimationHandler(Packet packet, Simulator simulator)
+ {
+ AvatarAnimationPacket animations = (AvatarAnimationPacket)packet;
+
+ Agent agent;
+ if (server.Agents.TryGetValue(animations.Sender.ID, out agent))
+ {
+ agent.Animations.Clear();
+
+ for (int i = 0; i < animations.AnimationList.Length; i++)
+ {
+ AvatarAnimationPacket.AnimationListBlock block = animations.AnimationList[i];
+ agent.Animations.Add(block.AnimID, block.AnimSequenceID);
+ }
+
+ server.Avatars.SendAnimations(agent);
+ }
+
+ if (animations.Sender.ID == client.Self.AgentID)
+ {
+ MasterAgent.Animations.Clear();
+
+ for (int i = 0; i < animations.AnimationList.Length; i++)
+ {
+ AvatarAnimationPacket.AnimationListBlock block = animations.AnimationList[i];
+ MasterAgent.Animations.Add(block.AnimID, block.AnimSequenceID);
+ }
+
+ server.Avatars.SendAnimations(MasterAgent);
+ }
+ }
+
+ void RegionHandshakeHandler(Packet packet, Simulator simulator)
+ {
+ RegionHandshakePacket handshake = (RegionHandshakePacket)packet;
+
+ handshake.RegionInfo.SimOwner = (MasterAgent != null ? MasterAgent.AgentID : UUID.Zero);
+
+ // TODO: Need more methods to manipulate the scene so we can apply these properties.
+ // Right now this only gets sent out to people who are logged in when the master avatar
+ // is already logged in
+ server.UDP.BroadcastPacket(handshake, PacketCategory.Transaction);
+ }
+
+ #region Simian client packet handlers
+
+ void ChatFromViewerHandler(Packet packet, Agent agent)
+ {
+ ChatFromViewerPacket chat = (ChatFromViewerPacket)packet;
+
+ // Forward chat from the viewer to the foreign simulator
+ string message = String.Format("<{0} {1}> {2}", agent.FirstName, agent.LastName,
+ Utils.BytesToString(chat.ChatData.Message));
+
+ client.Self.Chat(message, chat.ChatData.Channel, (ChatType)chat.ChatData.Type);
}
void AgentUpdateHandler(Packet packet, Agent agent)
{
AgentUpdatePacket update = (AgentUpdatePacket)packet;
- // Forward AgentUpdate packets with the AgentID/SessionID set to the bots ID
- update.AgentData.AgentID = client.Self.AgentID;
- update.AgentData.SessionID = client.Self.SessionID;
- client.Network.SendPacket(update);
+ if (MasterAgent == null)
+ {
+ lock (loginLock)
+ {
+ // Double-checked locking to avoid hitting the loginLock each time
+ if (MasterAgent == null &&
+ server.Agents.TryGetValue(update.AgentData.AgentID, out MasterAgent))
+ {
+ Logger.Log(String.Format("[Periscope] {0} {1} is the controlling agent",
+ MasterAgent.FirstName, MasterAgent.LastName), Helpers.LogLevel.Info);
+
+ LoginParams login = client.Network.DefaultLoginParams(FIRST_NAME, LAST_NAME, PASSWORD, "Simian Periscope",
+ "1.0.0");
+ login.Start = NetworkManager.StartLocation(SIMULATOR, 128, 128, 128);
+ client.Network.Login(login);
+
+ if (client.Network.Connected)
+ Logger.Log("[Periscope] Connected: " + client.Network.LoginMessage, Helpers.LogLevel.Info);
+ else
+ Logger.Log("[Periscope] Failed to connect to the foreign grid: " + client.Network.LoginErrorKey, Helpers.LogLevel.Error);
+ }
+ }
+ }
+
+ if (MasterAgent == null || update.AgentData.AgentID == MasterAgent.AgentID)
+ {
+ // Forward AgentUpdate packets with the AgentID/SessionID set to the bots ID
+ update.AgentData.AgentID = client.Self.AgentID;
+ update.AgentData.SessionID = client.Self.SessionID;
+ client.Network.SendPacket(update);
+ }
}
+
+ #endregion Simian client packet handlers
}
}
diff --git a/Programs/Simian/Extensions/PeriscopeImageDelivery.cs b/Programs/Simian/Extensions/PeriscopeImageDelivery.cs
new file mode 100644
index 00000000..e9478c3a
--- /dev/null
+++ b/Programs/Simian/Extensions/PeriscopeImageDelivery.cs
@@ -0,0 +1,205 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using ExtensionLoader;
+using OpenMetaverse;
+using OpenMetaverse.Packets;
+
+namespace Simian.Extensions
+{
+ public class PeriscopeImageDelivery
+ {
+ Simian server;
+ GridClient client;
+ TexturePipeline pipeline;
+ Dictionary currentDownloads = new Dictionary();
+
+ public PeriscopeImageDelivery(Simian server, GridClient client)
+ {
+ this.server = server;
+ this.client = client;
+
+ pipeline = new TexturePipeline(client, 10);
+ pipeline.OnDownloadFinished += new TexturePipeline.DownloadFinishedCallback(pipeline_OnDownloadFinished);
+
+ server.UDP.RegisterPacketCallback(PacketType.RequestImage, RequestImageHandler);
+ }
+
+ public void Stop()
+ {
+ pipeline.Shutdown();
+ }
+
+ void RequestImageHandler(Packet packet, Agent agent)
+ {
+ RequestImagePacket request = (RequestImagePacket)packet;
+
+ for (int i = 0; i < request.RequestImage.Length; i++)
+ {
+ RequestImagePacket.RequestImageBlock block = request.RequestImage[i];
+
+ ImageDownload download;
+ bool downloadFound = currentDownloads.TryGetValue(block.Image, out download);
+
+ if (downloadFound)
+ {
+ lock (download)
+ {
+ if (block.DiscardLevel == -1 && block.DownloadPriority == 0.0f)
+ Logger.DebugLog(String.Format("Image download {0} is aborting", block.Image));
+
+ // Update download
+ download.Update(block.DiscardLevel, block.DownloadPriority, (int)block.Packet);
+ }
+ }
+ else if (block.DiscardLevel == -1 && block.DownloadPriority == 0.0f)
+ {
+ // Aborting a download we are not tracking, ignore
+ }
+ else
+ {
+ bool bake = ((ImageType)block.Type == ImageType.Baked);
+
+ // New download, check if we have this image
+ Asset asset;
+ if (server.Assets.TryGetAsset(block.Image, out asset) && asset is AssetTexture)
+ {
+ SendTexture(agent, (AssetTexture)asset, block.DiscardLevel, (int)block.Packet, block.DownloadPriority);
+ }
+ else
+ {
+ // We don't have this texture, add it to the download queue and see if the bot can get it for us
+ download = new ImageDownload(null, agent, block.DiscardLevel, block.DownloadPriority, (int)block.Packet);
+ lock (currentDownloads)
+ currentDownloads[block.Image] = download;
+
+ pipeline.RequestTexture(block.Image, (ImageType)block.Type);
+ }
+ }
+ }
+ }
+
+ void pipeline_OnDownloadFinished(UUID id, bool success)
+ {
+ ImageDownload download;
+ if (currentDownloads.TryGetValue(id, out download))
+ {
+ lock (currentDownloads)
+ currentDownloads.Remove(id);
+
+ if (success)
+ {
+ // Set the texture to the downloaded texture data
+ AssetTexture texture = new AssetTexture(id, pipeline.GetTextureToRender(id).AssetData);
+ download.Texture = texture;
+
+ pipeline.RemoveFromPipeline(id);
+
+ // Store this texture in the local asset store for later
+ server.Assets.StoreAsset(texture);
+
+ SendTexture(download.Agent, download.Texture, download.DiscardLevel, download.CurrentPacket, download.Priority);
+ }
+ else
+ {
+ Logger.Log("[Periscope] Failed to download texture " + id.ToString(), Helpers.LogLevel.Warning);
+
+ ImageNotInDatabasePacket notfound = new ImageNotInDatabasePacket();
+ notfound.ImageID.ID = id;
+ server.UDP.SendPacket(download.Agent.AgentID, notfound, PacketCategory.Texture);
+ }
+ }
+ else
+ {
+ Logger.Log("[Periscope] Pipeline downloaded a texture we're not tracking, " + id.ToString(), Helpers.LogLevel.Warning);
+ }
+ }
+
+ void SendTexture(Agent agent, AssetTexture texture, int discardLevel, int packet, float priority)
+ {
+ ImageDownload download = new ImageDownload(texture, agent, discardLevel, priority, packet);
+
+ Logger.DebugLog(String.Format(
+ "[Periscope] Starting new texture transfer for {0}, DiscardLevel: {1}, Priority: {2}, Start: {3}, End: {4}, Total: {5}",
+ texture.AssetID, discardLevel, priority, download.CurrentPacket, download.StopPacket, download.TexturePacketCount()));
+
+ // Send initial data
+ ImageDataPacket data = new ImageDataPacket();
+ data.ImageID.Codec = (byte)ImageCodec.J2C;
+ data.ImageID.ID = download.Texture.AssetID;
+ data.ImageID.Packets = (ushort)download.TexturePacketCount();
+ data.ImageID.Size = (uint)download.Texture.AssetData.Length;
+
+ // The first bytes of the image are always sent in the ImageData packet
+ data.ImageData = new ImageDataPacket.ImageDataBlock();
+ int imageDataSize = (download.Texture.AssetData.Length >= ImageDownload.FIRST_IMAGE_PACKET_SIZE) ?
+ ImageDownload.FIRST_IMAGE_PACKET_SIZE : download.Texture.AssetData.Length;
+ try
+ {
+ data.ImageData.Data = new byte[imageDataSize];
+ Buffer.BlockCopy(download.Texture.AssetData, 0, data.ImageData.Data, 0, imageDataSize);
+ }
+ catch (Exception ex)
+ {
+ Logger.Log(String.Format("{0}: imageDataSize={1}", ex.Message, imageDataSize),
+ Helpers.LogLevel.Error);
+ }
+
+ server.UDP.SendPacket(agent.AgentID, data, PacketCategory.Texture);
+
+ // Check if ImagePacket packets need to be sent to complete this transfer
+ if (download.CurrentPacket <= download.StopPacket)
+ {
+ // Insert this download into the dictionary
+ lock (currentDownloads)
+ currentDownloads[texture.AssetID] = download;
+
+ // Send all of the remaining packets
+ ThreadPool.QueueUserWorkItem(
+ delegate(object obj)
+ {
+ while (download.CurrentPacket <= download.StopPacket)
+ {
+ if (download.Priority == 0.0f && download.DiscardLevel == -1)
+ break;
+
+ lock (download)
+ {
+ int imagePacketSize = (download.CurrentPacket == download.TexturePacketCount() - 1) ?
+ download.LastPacketSize() : ImageDownload.IMAGE_PACKET_SIZE;
+
+ ImagePacketPacket transfer = new ImagePacketPacket();
+ transfer.ImageID.ID = texture.AssetID;
+ transfer.ImageID.Packet = (ushort)download.CurrentPacket;
+ transfer.ImageData.Data = new byte[imagePacketSize];
+
+ try
+ {
+ Buffer.BlockCopy(download.Texture.AssetData, download.CurrentBytePosition(),
+ transfer.ImageData.Data, 0, imagePacketSize);
+ }
+ catch (Exception ex)
+ {
+ Logger.Log(String.Format(
+ "{0}: CurrentBytePosition()={1}, AssetData.Length={2} imagePacketSize={3}",
+ ex.Message, download.CurrentBytePosition(), download.Texture.AssetData.Length,
+ imagePacketSize), Helpers.LogLevel.Error);
+ }
+
+ server.UDP.SendPacket(agent.AgentID, transfer, PacketCategory.Texture);
+
+ ++download.CurrentPacket;
+ }
+ }
+
+ Logger.DebugLog("Completed image transfer for " + texture.AssetID.ToString());
+
+ // Transfer is complete, remove the reference
+ lock (currentDownloads)
+ currentDownloads.Remove(texture.AssetID);
+ }
+ );
+ }
+ }
+ }
+}
diff --git a/Programs/Simian/Extensions/PeriscopeMovement.cs b/Programs/Simian/Extensions/PeriscopeMovement.cs
new file mode 100644
index 00000000..bd8dc9e0
--- /dev/null
+++ b/Programs/Simian/Extensions/PeriscopeMovement.cs
@@ -0,0 +1,371 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using ExtensionLoader;
+using OpenMetaverse;
+using OpenMetaverse.Packets;
+
+namespace Simian.Extensions
+{
+ public class PeriscopeMovement
+ {
+ const int UPDATE_ITERATION = 100; //rate in milliseconds to send ObjectUpdate
+ const bool ENVIRONMENT_SOUNDS = true; //collision sounds, splashing, etc
+ const float GRAVITY = 9.8f; //meters/sec
+ const float WALK_SPEED = 3f; //meters/sec
+ const float RUN_SPEED = 5f; //meters/sec
+ const float FLY_SPEED = 10f; //meters/sec
+ const float FALL_DELAY = 0.33f; //seconds before starting animation
+ const float FALL_FORGIVENESS = 0.25f; //fall buffer in meters
+ const float JUMP_IMPULSE_VERTICAL = 8.5f; //boost amount in meters/sec
+ const float JUMP_IMPULSE_HORIZONTAL = 10f; //boost amount in meters/sec (no clue why this is so high)
+ const float INITIAL_HOVER_IMPULSE = 2f; //boost amount in meters/sec
+ const float PREJUMP_DELAY = 0.25f; //seconds before actually jumping
+ const float AVATAR_TERMINAL_VELOCITY = 54f; //~120mph
+
+ static readonly UUID BIG_SPLASH_SOUND = new UUID("486475b9-1460-4969-871e-fad973b38015");
+
+ const float SQRT_TWO = 1.41421356f;
+
+ Simian server;
+ Periscope periscope;
+ Timer updateTimer;
+ long lastTick;
+
+ public int LastTick
+ {
+ get { return (int)Interlocked.Read(ref lastTick); }
+ set { Interlocked.Exchange(ref lastTick, value); }
+ }
+
+ public PeriscopeMovement(Simian server, Periscope periscope)
+ {
+ this.server = server;
+ this.periscope = periscope;
+
+ server.UDP.RegisterPacketCallback(PacketType.AgentUpdate, AgentUpdateHandler);
+ server.UDP.RegisterPacketCallback(PacketType.SetAlwaysRun, SetAlwaysRunHandler);
+
+ updateTimer = new Timer(new TimerCallback(UpdateTimer_Elapsed));
+ LastTick = Environment.TickCount;
+ updateTimer.Change(UPDATE_ITERATION, UPDATE_ITERATION);
+ }
+
+ public void Stop()
+ {
+ if (updateTimer != null)
+ {
+ updateTimer.Dispose();
+ updateTimer = null;
+ }
+ }
+
+ void UpdateTimer_Elapsed(object sender)
+ {
+ int tick = Environment.TickCount;
+ float seconds = (float)((tick - LastTick) / 1000f);
+ LastTick = tick;
+
+ lock (server.Agents)
+ {
+ foreach (Agent agent in server.Agents.Values)
+ {
+ // Don't handle movement for the master agent or foreign agents
+ if (agent != periscope.MasterAgent && agent.SessionID != UUID.Zero)
+ {
+ bool animsChanged = false;
+
+ // Create forward and left vectors from the current avatar rotation
+ Matrix4 rotMatrix = Matrix4.CreateFromQuaternion(agent.Avatar.Rotation);
+ Vector3 fwd = Vector3.Transform(Vector3.UnitX, rotMatrix);
+ Vector3 left = Vector3.Transform(Vector3.UnitY, rotMatrix);
+
+ // Check control flags
+ bool heldForward = (agent.ControlFlags & AgentManager.ControlFlags.AGENT_CONTROL_AT_POS) == AgentManager.ControlFlags.AGENT_CONTROL_AT_POS;
+ bool heldBack = (agent.ControlFlags & AgentManager.ControlFlags.AGENT_CONTROL_AT_NEG) == AgentManager.ControlFlags.AGENT_CONTROL_AT_NEG;
+ bool heldLeft = (agent.ControlFlags & AgentManager.ControlFlags.AGENT_CONTROL_LEFT_POS) == AgentManager.ControlFlags.AGENT_CONTROL_LEFT_POS;
+ bool heldRight = (agent.ControlFlags & AgentManager.ControlFlags.AGENT_CONTROL_LEFT_NEG) == AgentManager.ControlFlags.AGENT_CONTROL_LEFT_NEG;
+ bool heldTurnLeft = (agent.ControlFlags & AgentManager.ControlFlags.AGENT_CONTROL_TURN_LEFT) == AgentManager.ControlFlags.AGENT_CONTROL_TURN_LEFT;
+ bool heldTurnRight = (agent.ControlFlags & AgentManager.ControlFlags.AGENT_CONTROL_TURN_RIGHT) == AgentManager.ControlFlags.AGENT_CONTROL_TURN_RIGHT;
+ bool heldUp = (agent.ControlFlags & AgentManager.ControlFlags.AGENT_CONTROL_UP_POS) == AgentManager.ControlFlags.AGENT_CONTROL_UP_POS;
+ bool heldDown = (agent.ControlFlags & AgentManager.ControlFlags.AGENT_CONTROL_UP_NEG) == AgentManager.ControlFlags.AGENT_CONTROL_UP_NEG;
+ bool flying = (agent.ControlFlags & AgentManager.ControlFlags.AGENT_CONTROL_FLY) == AgentManager.ControlFlags.AGENT_CONTROL_FLY;
+ bool mouselook = (agent.ControlFlags & AgentManager.ControlFlags.AGENT_CONTROL_MOUSELOOK) == AgentManager.ControlFlags.AGENT_CONTROL_MOUSELOOK;
+
+ // direction in which the avatar is trying to move
+ Vector3 move = Vector3.Zero;
+ if (heldForward) { move.X += fwd.X; move.Y += fwd.Y; }
+ if (heldBack) { move.X -= fwd.X; move.Y -= fwd.Y; }
+ if (heldLeft) { move.X += left.X; move.Y += left.Y; }
+ if (heldRight) { move.X -= left.X; move.Y -= left.Y; }
+ if (heldUp) { move.Z += 1; }
+ if (heldDown) { move.Z -= 1; }
+
+ // is the avatar trying to move?
+ bool moving = move != Vector3.Zero;
+ bool jumping = agent.TickJump != 0;
+
+ // 2-dimensional speed multipler
+ float speed = seconds * (flying ? FLY_SPEED : agent.Running && !jumping ? RUN_SPEED : WALK_SPEED);
+ if ((heldForward || heldBack) && (heldLeft || heldRight))
+ speed /= SQRT_TWO;
+
+ // adjust multiplier for Z dimension
+ float oldFloor = GetLandHeightAt(agent.Avatar.Position);
+ float newFloor = GetLandHeightAt(agent.Avatar.Position + (move * speed));
+ if (!flying && newFloor != oldFloor)
+ speed /= (1 + (SQRT_TWO * Math.Abs(newFloor - oldFloor)));
+
+ // least possible distance from avatar to the ground
+ // TODO: calculate to get rid of "bot squat"
+ float lowerLimit = newFloor + agent.Avatar.Scale.Z / 2;
+
+ // Z acceleration resulting from gravity
+ float gravity = 0f;
+
+ float waterChestHeight = server.Scene.WaterHeight - (agent.Avatar.Scale.Z * .33f);
+
+ if (flying)
+ {
+ agent.TickFall = 0;
+ agent.TickJump = 0;
+
+ //velocity falloff while flying
+ agent.Avatar.Velocity.X *= 0.66f;
+ agent.Avatar.Velocity.Y *= 0.66f;
+ agent.Avatar.Velocity.Z *= 0.33f;
+
+ if (agent.Avatar.Position.Z == lowerLimit)
+ agent.Avatar.Velocity.Z += INITIAL_HOVER_IMPULSE;
+
+ if (move.X != 0 || move.Y != 0)
+ { //flying horizontally
+ if (server.Avatars.SetDefaultAnimation(agent, Animations.FLY))
+ animsChanged = true;
+ }
+ else if (move.Z > 0)
+ { //flying straight up
+ if (server.Avatars.SetDefaultAnimation(agent, Animations.HOVER_UP))
+ animsChanged = true;
+ }
+ else if (move.Z < 0)
+ { //flying straight down
+ if (server.Avatars.SetDefaultAnimation(agent, Animations.HOVER_DOWN))
+ animsChanged = true;
+ }
+ else
+ { //hovering in the air
+ if (server.Avatars.SetDefaultAnimation(agent, Animations.HOVER))
+ animsChanged = true;
+ }
+ }
+ else if (agent.Avatar.Position.Z > lowerLimit + FALL_FORGIVENESS || agent.Avatar.Position.Z <= waterChestHeight)
+ { //falling, floating, or landing from a jump
+
+ if (agent.Avatar.Position.Z > server.Scene.WaterHeight)
+ { //above water
+
+ move = Vector3.Zero; //override controls while drifting
+ agent.Avatar.Velocity *= 0.95f; //keep most of our inertia
+
+ float fallElapsed = (float)(Environment.TickCount - agent.TickFall) / 1000f;
+
+ if (agent.TickFall == 0 || (fallElapsed > FALL_DELAY && agent.Avatar.Velocity.Z >= 0f))
+ { //just started falling
+ agent.TickFall = Environment.TickCount;
+ }
+ else
+ {
+ gravity = GRAVITY * fallElapsed * seconds; //normal gravity
+
+ if (!jumping)
+ { //falling
+ if (fallElapsed > FALL_DELAY)
+ { //falling long enough to trigger the animation
+ if (server.Avatars.SetDefaultAnimation(agent, Animations.FALLDOWN))
+ animsChanged = true;
+ }
+ }
+ }
+ }
+ }
+ else
+ { //on the ground
+
+ agent.TickFall = 0;
+
+ //friction
+ agent.Avatar.Acceleration *= 0.2f;
+ agent.Avatar.Velocity *= 0.2f;
+
+ agent.Avatar.Position.Z = lowerLimit;
+
+ if (move.Z > 0)
+ { //jumping
+ if (!jumping)
+ { //begin prejump
+ move.Z = 0; //override Z control
+ if (server.Avatars.SetDefaultAnimation(agent, Animations.PRE_JUMP))
+ animsChanged = true;
+
+ agent.TickJump = Environment.TickCount;
+ }
+ else if (Environment.TickCount - agent.TickJump > PREJUMP_DELAY * 1000)
+ { //start actual jump
+
+ if (agent.TickJump == -1)
+ {
+ //already jumping! end current jump
+ agent.TickJump = 0;
+ return;
+ }
+
+ if (server.Avatars.SetDefaultAnimation(agent, Animations.JUMP))
+ animsChanged = true;
+
+ agent.Avatar.Velocity.X += agent.Avatar.Acceleration.X * JUMP_IMPULSE_HORIZONTAL;
+ agent.Avatar.Velocity.Y += agent.Avatar.Acceleration.Y * JUMP_IMPULSE_HORIZONTAL;
+ agent.Avatar.Velocity.Z = JUMP_IMPULSE_VERTICAL * seconds;
+
+ agent.TickJump = -1; //flag that we are currently jumping
+ }
+ else
+ {
+ move.Z = 0; //override Z control
+ }
+ }
+ else
+ { //not jumping
+
+ agent.TickJump = 0;
+
+ if (move.X != 0 || move.Y != 0)
+ { //not walking
+
+ if (move.Z < 0)
+ { //crouchwalking
+ if (server.Avatars.SetDefaultAnimation(agent, Animations.CROUCHWALK))
+ animsChanged = true;
+ }
+ else if (agent.Running)
+ { //running
+ if (server.Avatars.SetDefaultAnimation(agent, Animations.RUN))
+ animsChanged = true;
+ }
+ else
+ { //walking
+ if (server.Avatars.SetDefaultAnimation(agent, Animations.WALK))
+ animsChanged = true;
+ }
+ }
+ else
+ { //walking
+ if (move.Z < 0)
+ { //crouching
+ if (server.Avatars.SetDefaultAnimation(agent, Animations.CROUCH))
+ animsChanged = true;
+ }
+ else
+ { //standing
+ if (server.Avatars.SetDefaultAnimation(agent, Animations.STAND))
+ animsChanged = true;
+ }
+ }
+ }
+ }
+
+ if (animsChanged)
+ server.Avatars.SendAnimations(agent);
+
+ float maxVel = AVATAR_TERMINAL_VELOCITY * seconds;
+
+ // static acceleration when any control is held, otherwise none
+ if (moving)
+ {
+ agent.Avatar.Acceleration = move * speed;
+ if (agent.Avatar.Acceleration.Z < -maxVel)
+ agent.Avatar.Acceleration.Z = -maxVel;
+ else if (agent.Avatar.Acceleration.Z > maxVel)
+ agent.Avatar.Acceleration.Z = maxVel;
+ }
+ else
+ {
+ agent.Avatar.Acceleration = Vector3.Zero;
+ }
+
+ agent.Avatar.Velocity += agent.Avatar.Acceleration - new Vector3(0f, 0f, gravity);
+ if (agent.Avatar.Velocity.Z < -maxVel)
+ agent.Avatar.Velocity.Z = -maxVel;
+ else if (agent.Avatar.Velocity.Z > maxVel)
+ agent.Avatar.Velocity.Z = maxVel;
+
+ agent.Avatar.Position += agent.Avatar.Velocity;
+
+ if (agent.Avatar.Position.X < 0) agent.Avatar.Position.X = 0f;
+ else if (agent.Avatar.Position.X > 255) agent.Avatar.Position.X = 255f;
+
+ if (agent.Avatar.Position.Y < 0) agent.Avatar.Position.Y = 0f;
+ else if (agent.Avatar.Position.Y > 255) agent.Avatar.Position.Y = 255f;
+
+ if (agent.Avatar.Position.Z < lowerLimit) agent.Avatar.Position.Z = lowerLimit;
+ }
+ }
+ }
+ }
+
+ void AgentUpdateHandler(Packet packet, Agent agent)
+ {
+ AgentUpdatePacket update = (AgentUpdatePacket)packet;
+
+ // Don't use the local physics to update the master agent
+ if (agent != periscope.MasterAgent)
+ {
+ agent.Avatar.Rotation = update.AgentData.BodyRotation;
+ agent.ControlFlags = (AgentManager.ControlFlags)update.AgentData.ControlFlags;
+ agent.State = update.AgentData.State;
+ agent.Flags = (PrimFlags)update.AgentData.Flags;
+ }
+
+ ObjectUpdatePacket fullUpdate = SimulationObject.BuildFullUpdate(agent.Avatar,
+ server.RegionHandle, agent.State, agent.Flags);
+
+ server.UDP.BroadcastPacket(fullUpdate, PacketCategory.State);
+ }
+
+ void SetAlwaysRunHandler(Packet packet, Agent agent)
+ {
+ SetAlwaysRunPacket run = (SetAlwaysRunPacket)packet;
+
+ agent.Running = run.AgentData.AlwaysRun;
+ }
+
+ float GetLandHeightAt(Vector3 position)
+ {
+ int x = (int)position.X;
+ int y = (int)position.Y;
+
+ if (x > 255) x = 255;
+ else if (x < 0) x = 0;
+ if (y > 255) y = 255;
+ else if (y < 0) y = 0;
+
+ float center = server.Scene.Heightmap[y * 256 + x];
+ float distX = position.X - (int)position.X;
+ float distY = position.Y - (int)position.Y;
+
+ float nearestX;
+ float nearestY;
+
+ if (distX > 0) nearestX = server.Scene.Heightmap[y * 256 + x + (x < 255 ? 1 : 0)];
+ else nearestX = server.Scene.Heightmap[y * 256 + x - (x > 0 ? 1 : 0)];
+
+ if (distY > 0) nearestY = server.Scene.Heightmap[(y + (y < 255 ? 1 : 0)) * 256 + x];
+ else nearestY = server.Scene.Heightmap[(y - (y > 0 ? 1 : 0)) * 256 + x];
+
+ float lerpX = Utils.Lerp(center, nearestX, Math.Abs(distX));
+ float lerpY = Utils.Lerp(center, nearestY, Math.Abs(distY));
+
+ return ((lerpX + lerpY) / 2);
+ }
+ }
+}
diff --git a/Programs/Simian/Extensions/SceneManager.cs b/Programs/Simian/Extensions/SceneManager.cs
index 916c414e..242add02 100644
--- a/Programs/Simian/Extensions/SceneManager.cs
+++ b/Programs/Simian/Extensions/SceneManager.cs
@@ -61,25 +61,23 @@ namespace Simian.Extensions
{
// Check if the object already exists in the scene
if (sceneObjects.ContainsKey(obj.Prim.ID))
- {
- ObjectModify(sender, obj, obj.Prim.PrimData);
- return false;
- }
+ sceneObjects.Remove(obj.Prim.LocalID, obj.Prim.ID);
- // Assign a unique LocalID to this object
- obj.Prim.LocalID = (uint)Interlocked.Increment(ref currentLocalID);
+ if (obj.Prim.LocalID == 0)
+ {
+ // Assign a unique LocalID to this object
+ obj.Prim.LocalID = (uint)Interlocked.Increment(ref currentLocalID);
+ }
if (OnObjectAdd != null)
- {
OnObjectAdd(sender, obj, creatorFlags);
- }
// Add the object to the scene dictionary
sceneObjects.Add(obj.Prim.LocalID, obj.Prim.ID, obj);
- // Send an update out to the creator
if (server.Agents.ContainsKey(obj.Prim.OwnerID))
{
+ // Send an update out to the creator
ObjectUpdatePacket updateToOwner = SimulationObject.BuildFullUpdate(obj.Prim, server.RegionHandle, 0,
obj.Prim.Flags | creatorFlags);
server.UDP.SendPacket(obj.Prim.OwnerID, updateToOwner, PacketCategory.State);
@@ -100,45 +98,52 @@ namespace Simian.Extensions
return true;
}
- public bool ObjectRemove(object sender, SimulationObject obj)
+ public bool ObjectRemove(object sender, uint localID)
{
- if (OnObjectRemove != null)
+ SimulationObject obj;
+ if (sceneObjects.TryGetValue(localID, out obj))
{
- OnObjectRemove(sender, obj);
+ if (OnObjectRemove != null)
+ OnObjectRemove(sender, obj);
+
+ sceneObjects.Remove(localID, obj.Prim.ID);
+
+ KillObjectPacket kill = new KillObjectPacket();
+ kill.ObjectData = new KillObjectPacket.ObjectDataBlock[1];
+ kill.ObjectData[0] = new KillObjectPacket.ObjectDataBlock();
+ kill.ObjectData[0].ID = obj.Prim.LocalID;
+
+ server.UDP.BroadcastPacket(kill, PacketCategory.State);
+ return true;
+ }
+ else
+ {
+ return false;
}
-
- sceneObjects.Remove(obj.Prim.LocalID, obj.Prim.ID);
-
- KillObjectPacket kill = new KillObjectPacket();
- kill.ObjectData = new KillObjectPacket.ObjectDataBlock[1];
- kill.ObjectData[0] = new KillObjectPacket.ObjectDataBlock();
- kill.ObjectData[0].ID = obj.Prim.LocalID;
-
- server.UDP.BroadcastPacket(kill, PacketCategory.State);
-
- return true;
}
- public void ObjectTransform(object sender, SimulationObject obj, Vector3 position,
- Quaternion rotation, Vector3 velocity, Vector3 acceleration, Vector3 angularVelocity,
- Vector3 scale)
+ public void ObjectTransform(object sender, uint localID, Vector3 position, Quaternion rotation,
+ Vector3 velocity, Vector3 acceleration, Vector3 angularVelocity)
{
- if (OnObjectTransform != null)
+ SimulationObject obj;
+ if (sceneObjects.TryGetValue(localID, out obj))
{
- OnObjectTransform(sender, obj, position, rotation, velocity,
- acceleration, angularVelocity, scale);
+ if (OnObjectTransform != null)
+ {
+ OnObjectTransform(sender, obj, position, rotation, velocity,
+ acceleration, angularVelocity);
+ }
+
+ // Update the object
+ obj.Prim.Position = position;
+ obj.Prim.Rotation = rotation;
+ obj.Prim.Velocity = velocity;
+ obj.Prim.Acceleration = acceleration;
+ obj.Prim.AngularVelocity = angularVelocity;
+
+ // Inform clients
+ BroadcastObjectUpdate(obj);
}
-
- // Update the object
- obj.Prim.Position = position;
- obj.Prim.Rotation = rotation;
- obj.Prim.Velocity = velocity;
- obj.Prim.Acceleration = acceleration;
- obj.Prim.AngularVelocity = angularVelocity;
- obj.Prim.Scale = scale;
-
- // Inform clients
- BroadcastObjectUpdate(obj);
}
public void ObjectFlags(object sender, SimulationObject obj, PrimFlags flags)
@@ -170,18 +175,22 @@ namespace Simian.Extensions
BroadcastObjectUpdate(obj);
}
- public void ObjectModify(object sender, SimulationObject obj, Primitive.ConstructionData data)
+ public void ObjectModify(object sender, uint localID, Primitive.ConstructionData data)
{
- if (OnObjectModify != null)
+ SimulationObject obj;
+ if (sceneObjects.TryGetValue(localID, out obj))
{
- OnObjectModify(sender, obj, data);
+ if (OnObjectModify != null)
+ {
+ OnObjectModify(sender, obj, data);
+ }
+
+ // Update the object
+ obj.Prim.PrimData = data;
+
+ // Inform clients
+ BroadcastObjectUpdate(obj);
}
-
- // Update the object
- obj.Prim.PrimData = data;
-
- // Inform clients
- BroadcastObjectUpdate(obj);
}
public void AvatarAppearance(object sender, Agent agent, Primitive.TextureEntry textures, byte[] visualParams)
@@ -191,24 +200,28 @@ namespace Simian.Extensions
OnAvatarAppearance(sender, agent, textures, visualParams);
}
- // Update the avatar
- agent.Avatar.Textures = textures;
- if (visualParams != null)
- agent.VisualParams = visualParams;
-
- // Broadcast the object update
+ // Broadcast an object update for this avatar
+ // TODO: Is this necessary here?
ObjectUpdatePacket update = SimulationObject.BuildFullUpdate(agent.Avatar,
server.RegionHandle, agent.State, agent.Flags);
server.UDP.BroadcastPacket(update, PacketCategory.State);
- // Send the appearance packet to all other clients
- AvatarAppearancePacket appearance = BuildAppearancePacket(agent);
- lock (server.Agents)
+ // Update the avatar
+ agent.Avatar.Textures = textures;
+ if (visualParams != null && visualParams.Length > 1)
+ agent.VisualParams = visualParams;
+
+ if (agent.VisualParams != null)
{
- foreach (Agent recipient in server.Agents.Values)
+ // Send the appearance packet to all other clients
+ AvatarAppearancePacket appearance = BuildAppearancePacket(agent);
+ lock (server.Agents)
{
- if (recipient != agent)
- server.UDP.SendPacket(recipient.AgentID, appearance, PacketCategory.State);
+ foreach (Agent recipient in server.Agents.Values)
+ {
+ if (recipient != agent)
+ server.UDP.SendPacket(recipient.AgentID, appearance, PacketCategory.State);
+ }
}
}
}
@@ -384,13 +397,17 @@ namespace Simian.Extensions
appearance.Sender.ID = agent.AgentID;
appearance.Sender.IsTrial = false;
- appearance.VisualParam = new AvatarAppearancePacket.VisualParamBlock[218];
- for (int i = 0; i < 218; i++)
+ appearance.VisualParam = new AvatarAppearancePacket.VisualParamBlock[agent.VisualParams.Length];
+ for (int i = 0; i < agent.VisualParams.Length; i++)
{
appearance.VisualParam[i] = new AvatarAppearancePacket.VisualParamBlock();
appearance.VisualParam[i].ParamValue = agent.VisualParams[i];
}
+ if (agent.VisualParams.Length != 218)
+ Logger.Log("Built an appearance packet with VisualParams.Length=" + agent.VisualParams.Length,
+ Helpers.LogLevel.Warning);
+
return appearance;
}
}
diff --git a/Programs/Simian/Extensions/UDPManager.cs b/Programs/Simian/Extensions/UDPManager.cs
index 2f2584c1..e1a2166d 100644
--- a/Programs/Simian/Extensions/UDPManager.cs
+++ b/Programs/Simian/Extensions/UDPManager.cs
@@ -197,14 +197,8 @@ namespace Simian
{
// Look up the UDPClient this is going to
UDPClient client;
- if (!clients.TryGetValue(agentID, out client))
- {
- Logger.Log("Attempted to send a packet to unknown UDP client " +
- agentID.ToString(), Helpers.LogLevel.Warning);
- return;
- }
-
- SendPacket(client, new OutgoingPacket(packet, category));
+ if (clients.TryGetValue(agentID, out client))
+ SendPacket(client, new OutgoingPacket(packet, category));
}
void SendPacket(UDPClient client, OutgoingPacket outgoingPacket)
diff --git a/Programs/Simian/Interfaces/IAvatarProvider.cs b/Programs/Simian/Interfaces/IAvatarProvider.cs
index d00b96e2..44e18d7d 100644
--- a/Programs/Simian/Interfaces/IAvatarProvider.cs
+++ b/Programs/Simian/Interfaces/IAvatarProvider.cs
@@ -8,6 +8,7 @@ namespace Simian
bool SetDefaultAnimation(Agent agent, UUID animID);
bool AddAnimation(Agent agent, UUID animID);
bool RemoveAnimation(Agent agent, UUID animID);
+ bool ClearAnimations(Agent agent);
void SendAnimations(Agent agent);
void Disconnect(Agent agent);
}
diff --git a/Programs/Simian/Interfaces/ISceneProvider.cs b/Programs/Simian/Interfaces/ISceneProvider.cs
index f06ecda8..6b21e3e1 100644
--- a/Programs/Simian/Interfaces/ISceneProvider.cs
+++ b/Programs/Simian/Interfaces/ISceneProvider.cs
@@ -7,7 +7,7 @@ namespace Simian
public delegate void ObjectRemoveCallback(object sender, SimulationObject obj);
public delegate void ObjectTransformCallback(object sender, SimulationObject obj,
Vector3 position, Quaternion rotation, Vector3 velocity, Vector3 acceleration,
- Vector3 angularVelocity, Vector3 scale);
+ Vector3 angularVelocity);
public delegate void ObjectFlagsCallback(object sender, SimulationObject obj, PrimFlags flags);
public delegate void ObjectImageCallback(object sender, SimulationObject obj,
string mediaURL, Primitive.TextureEntry textureEntry);
@@ -34,13 +34,12 @@ namespace Simian
float WaterHeight { get; }
bool ObjectAdd(object sender, SimulationObject obj, PrimFlags creatorFlags);
- bool ObjectRemove(object sender, SimulationObject obj);
- void ObjectTransform(object sender, SimulationObject obj, Vector3 position,
- Quaternion rotation, Vector3 velocity, Vector3 acceleration,
- Vector3 angularVelocity, Vector3 scale);
+ bool ObjectRemove(object sender, uint localID);
+ void ObjectTransform(object sender, uint localID, Vector3 position, Quaternion rotation, Vector3 velocity,
+ Vector3 acceleration, Vector3 angularVelocity);
void ObjectFlags(object sender, SimulationObject obj, PrimFlags flags);
void ObjectImage(object sender, SimulationObject obj, string mediaURL, Primitive.TextureEntry textureEntry);
- void ObjectModify(object sender, SimulationObject obj, Primitive.ConstructionData data);
+ void ObjectModify(object sender, uint localID, Primitive.ConstructionData data);
void AvatarAppearance(object sender, Agent agent, Primitive.TextureEntry textures, byte[] visualParams);
diff --git a/bin/Simian.ini b/bin/Simian.ini
index 42954ce0..16121cce 100644
--- a/bin/Simian.ini
+++ b/bin/Simian.ini
@@ -39,15 +39,15 @@ AvatarManager
; Friendship management and alerts
FriendManager
-; Texture downloads
-ImageDelivery
-
; Chat and instant messaging
Messaging
; Money management and accounting functions
Money
+; Texture downloads
+ImageDelivery
+
; A simple physics engine for avatar movement. Supports walking, flying, and
; swimming as well as avatar-avatar collisions. Does not support avatar-prim
; or prim-prim collisions.
@@ -67,6 +67,15 @@ RenderingPluginMesher
; Main scene graph engine. All spatial events are processed through here.
SceneManager
+; Periscope allows you to proxy a foreign grid simulator into the local Simian
+; using a libOpenMetaverse bot. The first person to login to Simian will become
+; the master agent, who's movement is tethered to the bot's movement. Any other
+; agents that login can move around freely, but will only see what the master
+; agent is seeing through the periscope. If you enable this extension, disable
+; ImageDelivery and Movement as Periscope has its own implementations of those
+; extensions
+;Periscope
+
;
; ---Persistence Providers---
;