Package com.ngt.jopenmetaverse.shared.sim

Source Code of com.ngt.jopenmetaverse.shared.sim.ObjectManager

/**
* A library to interact with Virtual Worlds such as OpenSim
* Copyright (C) 2012  Jitendra Chauhan, Email: jitendra.chauhan@gmail.com
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License,
* or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.ngt.jopenmetaverse.shared.sim;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Observable;
import java.util.Timer;
import java.util.TimerTask;

import com.ngt.jopenmetaverse.shared.cap.http.CapsHttpClient;
import com.ngt.jopenmetaverse.shared.cap.http.CapsHttpRequestCompletedArg;
import com.ngt.jopenmetaverse.shared.exception.NotSupportedException;
import com.ngt.jopenmetaverse.shared.protocol.ImprovedTerseObjectUpdatePacket;
import com.ngt.jopenmetaverse.shared.protocol.KillObjectPacket;
import com.ngt.jopenmetaverse.shared.protocol.MultipleObjectUpdatePacket;
import com.ngt.jopenmetaverse.shared.protocol.NameValue;
import com.ngt.jopenmetaverse.shared.protocol.ObjectAddPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectAttachPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectBuyPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectDeGrabPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectDelinkPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectDescriptionPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectDeselectPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectDetachPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectDropPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectExtraParamsPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectFlagUpdatePacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectGrabPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectGroupPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectImagePacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectLinkPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectMaterialPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectNamePacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectOwnerPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectPermissionsPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectPropertiesFamilyPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectPropertiesPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectRotationPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectSaleInfoPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectSelectPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectShapePacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectUpdateCachedPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectUpdateCompressedPacket;
import com.ngt.jopenmetaverse.shared.protocol.ObjectUpdatePacket;
import com.ngt.jopenmetaverse.shared.protocol.Packet;
import com.ngt.jopenmetaverse.shared.protocol.PacketType;
import com.ngt.jopenmetaverse.shared.protocol.PayPriceReplyPacket;
import com.ngt.jopenmetaverse.shared.protocol.RequestMultipleObjectsPacket;
import com.ngt.jopenmetaverse.shared.protocol.RequestObjectPropertiesFamilyPacket;
import com.ngt.jopenmetaverse.shared.protocol.RequestPayPricePacket;
import com.ngt.jopenmetaverse.shared.protocol.primitives.ConstructionData;
import com.ngt.jopenmetaverse.shared.protocol.primitives.FlexibleData;
import com.ngt.jopenmetaverse.shared.protocol.primitives.LightData;
import com.ngt.jopenmetaverse.shared.protocol.primitives.MediaEntry;
import com.ngt.jopenmetaverse.shared.protocol.primitives.ObjectProperties;
import com.ngt.jopenmetaverse.shared.protocol.primitives.ParticleSystem;
import com.ngt.jopenmetaverse.shared.protocol.primitives.Permissions;
import com.ngt.jopenmetaverse.shared.protocol.primitives.Permissions.PermissionMask;
import com.ngt.jopenmetaverse.shared.protocol.primitives.Permissions.PermissionWho;
import com.ngt.jopenmetaverse.shared.protocol.primitives.Primitive;
import com.ngt.jopenmetaverse.shared.protocol.primitives.SculptData;
import com.ngt.jopenmetaverse.shared.protocol.primitives.TextureAnimation;
import com.ngt.jopenmetaverse.shared.protocol.primitives.TextureEntry;
import com.ngt.jopenmetaverse.shared.sim.Avatar;
import com.ngt.jopenmetaverse.shared.sim.events.CapsEventObservableArg;
import com.ngt.jopenmetaverse.shared.sim.events.EventObservable;
import com.ngt.jopenmetaverse.shared.sim.events.EventObserver;
import com.ngt.jopenmetaverse.shared.sim.events.PacketReceivedEventArgs;
import com.ngt.jopenmetaverse.shared.sim.events.ThreadPool;
import com.ngt.jopenmetaverse.shared.sim.events.ThreadPoolFactory;
import com.ngt.jopenmetaverse.shared.sim.events.om.AvatarSitChangedEventArgs;
import com.ngt.jopenmetaverse.shared.sim.events.om.AvatarUpdateEventArgs;
import com.ngt.jopenmetaverse.shared.sim.events.om.KillObjectEventArgs;
import com.ngt.jopenmetaverse.shared.sim.events.om.ObjectDataBlockUpdateEventArgs;
import com.ngt.jopenmetaverse.shared.sim.events.om.ObjectMediaCallbackArgs;
import com.ngt.jopenmetaverse.shared.sim.events.om.ObjectPropertiesEventArgs;
import com.ngt.jopenmetaverse.shared.sim.events.om.ObjectPropertiesFamilyEventArgs;
import com.ngt.jopenmetaverse.shared.sim.events.om.ObjectPropertiesUpdatedEventArgs;
import com.ngt.jopenmetaverse.shared.sim.events.om.PayPriceReplyEventArgs;
import com.ngt.jopenmetaverse.shared.sim.events.om.PhysicsPropertiesEventArgs;
import com.ngt.jopenmetaverse.shared.sim.events.om.PrimEventArgs;
import com.ngt.jopenmetaverse.shared.sim.events.om.TerseObjectUpdateEventArgs;
import com.ngt.jopenmetaverse.shared.sim.interfaces.IMessage;
import com.ngt.jopenmetaverse.shared.sim.message.LindenMessages;
import com.ngt.jopenmetaverse.shared.sim.message.LindenMessages.ObjectMediaNavigateMessage;
import com.ngt.jopenmetaverse.shared.sim.message.LindenMessages.ObjectPhysicsPropertiesMessage;
import com.ngt.jopenmetaverse.shared.structureddata.OSDFormat;
import com.ngt.jopenmetaverse.shared.structureddata.OSDMap;
import com.ngt.jopenmetaverse.shared.types.Action;
import com.ngt.jopenmetaverse.shared.types.Color4;
import com.ngt.jopenmetaverse.shared.types.Enums.SaleType;
import com.ngt.jopenmetaverse.shared.types.EnumsPrimitive.AttachmentPoint;
import com.ngt.jopenmetaverse.shared.types.EnumsPrimitive.ClickAction;
import com.ngt.jopenmetaverse.shared.types.EnumsPrimitive.ExtraParamType;
import com.ngt.jopenmetaverse.shared.types.EnumsPrimitive.Grass;
import com.ngt.jopenmetaverse.shared.types.EnumsPrimitive.JointType;
import com.ngt.jopenmetaverse.shared.types.EnumsPrimitive.Material;
import com.ngt.jopenmetaverse.shared.types.EnumsPrimitive.ObjectCategory;
import com.ngt.jopenmetaverse.shared.types.EnumsPrimitive.PCode;
import com.ngt.jopenmetaverse.shared.types.EnumsPrimitive.PathCurve;
import com.ngt.jopenmetaverse.shared.types.EnumsPrimitive.PhysicsShapeType;
import com.ngt.jopenmetaverse.shared.types.EnumsPrimitive.PrimFlags;
import com.ngt.jopenmetaverse.shared.types.EnumsPrimitive.PrimType;
import com.ngt.jopenmetaverse.shared.types.EnumsPrimitive.ProfileCurve;
import com.ngt.jopenmetaverse.shared.types.EnumsPrimitive.Tree;
import com.ngt.jopenmetaverse.shared.types.Quaternion;
import com.ngt.jopenmetaverse.shared.types.UUID;
import com.ngt.jopenmetaverse.shared.types.Vector3;
import com.ngt.jopenmetaverse.shared.types.Vector4;
import com.ngt.jopenmetaverse.shared.util.JLogger;
import com.ngt.jopenmetaverse.shared.util.Utils;

/// <summary>
/// Handles all network traffic related to prims and avatar positions and
/// movement.
/// </summary>
public class ObjectManager {
  //region Enums

  /// <summary>
  ///
  /// </summary>
  public enum ReportType
  {
    /// <summary>No report</summary>
    None ((long)0),
    /// <summary>Unknown report type</summary>
    Unknown ((long)1),
    /// <summary>Bug report</summary>
    Bug ((long)2),
    /// <summary>Complaint report</summary>
    Complaint ((long)3),
    /// <summary>Customer service report</summary>
    CustomerServiceRequest ((long)4);
    private long index;
    ReportType(long index)
    {
      this.index = index;
    }    

    public long getIndex()
    {
      return index;
   

    private static final Map<Long,ReportType> lookup  = new HashMap<Long,ReportType>();

    static {
      for(ReportType s : EnumSet.allOf(ReportType.class))
        lookup.put(s.getIndex(), s);
    }

    public static ReportType get(Long index)
    {
      return lookup.get(index);
    }
  }

  /// <summary>
  /// Bitflag field for ObjectUpdateCompressed data blocks, describing
  /// which options are present for each object
  /// </summary>

  public enum CompressedFlags
  {
    None ((long)0x00),
    /// <summary>Unknown</summary>
    ScratchPad ((long)0x01),
    /// <summary>Whether the object has a TreeSpecies</summary>
    Tree ((long)0x02),
    /// <summary>Whether the object has floating text ala llSetText</summary>
    HasText ((long)0x04),
    /// <summary>Whether the object has an active particle system</summary>
    HasParticles ((long)0x08),
    /// <summary>Whether the object has sound attached to it</summary>
    HasSound ((long)0x10),
    /// <summary>Whether the object is attached to a root object or not</summary>
    HasParent ((long)0x20),
    /// <summary>Whether the object has texture animation settings</summary>
    TextureAnimation ((long)0x40),
    /// <summary>Whether the object has an angular velocity</summary>
    HasAngularVelocity ((long)0x80),
    /// <summary>Whether the object has a name value pairs String</summary>
    HasNameValues ((long)0x100),
    /// <summary>Whether the object has a Media URL set</summary>
    MediaURL((long)0x200);
    private long index;
    CompressedFlags(long index)
    {
      this.index = index;
    }    

    public long getIndex()
    {
      return index;
   

    private static final Map<Long,CompressedFlags> lookup  = new HashMap<Long,CompressedFlags>();

    static {
      for(CompressedFlags s : EnumSet.allOf(CompressedFlags.class))
        lookup.put(s.getIndex(), s);
    }

        public static EnumSet<CompressedFlags> get(Long index)
        {
                EnumSet<CompressedFlags> enumsSet = EnumSet.allOf(CompressedFlags.class);
                for(Entry<Long,CompressedFlags> entry: lookup.entrySet())
                {
                        if((entry.getKey().longValue() | index) != index)
                        {
                                enumsSet.remove(entry.getValue());
                        }
                }
                return enumsSet;
        }

        public static long getIndex(EnumSet<CompressedFlags> enumSet)
        {
                long ret = 0;
                for(CompressedFlags s: enumSet)
                {
                        ret |= s.getIndex();
                }
                return ret;
        }
  }

  /// <summary>
  /// Specific Flags for MultipleObjectUpdate requests
  /// </summary>

  public enum UpdateType
  {
    /// <summary>None</summary>
    None ((long)0x00),
    /// <summary>Change position of prims</summary>
    Position ((long)0x01),
    /// <summary>Change rotation of prims</summary>
    Rotation ((long)0x02),
    /// <summary>Change size of prims</summary>
    Scale ((long)0x04),
    /// <summary>Perform operation on link set</summary>
    Linked ((long)0x08),
    /// <summary>Scale prims uniformly, same as selecing ctrl+shift in the
    /// viewer. Used in conjunction with Scale</summary>
    Uniform ((long)0x10);
    private long index;
    UpdateType(long index)
    {
      this.index = index;
    }    

    public long getIndex()
    {
      return index;
   

    private static final Map<Long,UpdateType> lookup  = new HashMap<Long,UpdateType>();

    static {
      for(UpdateType s : EnumSet.allOf(UpdateType.class))
        lookup.put(s.getIndex(), s);
    }

        public static EnumSet<UpdateType> get(Long index)
        {
                EnumSet<UpdateType> enumsSet = EnumSet.allOf(UpdateType.class);
                for(Entry<Long,UpdateType> entry: lookup.entrySet())
                {
                        if((entry.getKey().longValue() | index) != index)
                        {
                                enumsSet.remove(entry.getValue());
                        }
                }
                return enumsSet;
        }

        public static long getIndex(EnumSet<UpdateType> enumSet)
        {
                long ret = 0;
                for(UpdateType s: enumSet)
                {
                        ret |= s.getIndex();
                }
                return ret;
        }

  }

  /// <summary>
  /// Special values in PayPriceReply. If the price is not one of these
  /// literal value of the price should be use
  /// </summary>
  public enum PayPriceType
  {
    /// <summary>
    /// Indicates that this pay option should be hidden
    /// </summary>
    Hide(-1),

    /// <summary>
    /// Indicates that this pay option should have the default value
    /// </summary>
    Default(-2);
    private int index;
    PayPriceType(int index)
    {
      this.index = index;
    }    

    public int getIndex()
    {
      return index;
    }

    private static final Map<Integer,PayPriceType> lookup 
    = new HashMap<Integer,PayPriceType>();

    static {
      for(PayPriceType s : EnumSet.allOf(PayPriceType.class))
        lookup.put(s.getIndex(), s);
    }

    public static PayPriceType get(Integer index)
    {
      return lookup.get(index);
    }
  }

  //endregion Enums

  //region Structs

  /// <summary>
  /// Contains the variables sent in an object update packet for objects.
  /// Used to track position and movement of prims and avatars
  /// </summary>
  public class ObjectMovementUpdate
  {
    /// <summary></summary>
    public boolean Avatar;
    /// <summary></summary>
    public Vector4 CollisionPlane;
    /// <summary></summary>
    public byte State;
    /// <summary></summary>
    //uint
    public long LocalID;
    /// <summary></summary>
    public Vector3 Position;
    /// <summary></summary>
    public Vector3 Velocity;
    /// <summary></summary>
    public Vector3 Acceleration;
    /// <summary></summary>
    public Quaternion Rotation;
    /// <summary></summary>
    public Vector3 AngularVelocity;
    /// <summary></summary>
    public TextureEntry Textures;
  }

  //endregion Structs

  public final float HAVOK_TIMESTEP = 1.0f / 45.0f;

  //region Delegates

  private EventObservable<PrimEventArgs> onObjectUpdate = new EventObservable<PrimEventArgs>();
  public void registerOnObjectUpdate(EventObserver<PrimEventArgs> o)
  {
    onObjectUpdate.addObserver(o);
  }
  public void unregisterOnObjectUpdate(EventObserver<PrimEventArgs> o)
  {
    onObjectUpdate.addObserver(o);
  }
  private EventObservable<ObjectPropertiesEventArgs> onObjectProperties = new EventObservable<ObjectPropertiesEventArgs>();
  public void registerOnObjectProperties(EventObserver<ObjectPropertiesEventArgs> o)
  {
    onObjectProperties.addObserver(o);
  }
  public void unregisterOnObjectProperties(EventObserver<ObjectPropertiesEventArgs> o)
  {
    onObjectProperties.addObserver(o);
  }
  private EventObservable<ObjectPropertiesUpdatedEventArgs> onObjectPropertiesUpdated = new EventObservable<ObjectPropertiesUpdatedEventArgs>();
  public void registerOnObjectPropertiesUpdated(EventObserver<ObjectPropertiesUpdatedEventArgs> o)
  {
    onObjectPropertiesUpdated.addObserver(o);
  }
  public void unregisterOnObjectPropertiesUpdated(EventObserver<ObjectPropertiesUpdatedEventArgs> o)
  {
    onObjectPropertiesUpdated.addObserver(o);
  }
  private EventObservable<ObjectPropertiesFamilyEventArgs> onObjectPropertiesFamily = new EventObservable<ObjectPropertiesFamilyEventArgs>();
  public void registerOnObjectPropertiesFamily(EventObserver<ObjectPropertiesFamilyEventArgs> o)
  {
    onObjectPropertiesFamily.addObserver(o);
  }
  public void unregisterOnObjectPropertiesFamily(EventObserver<ObjectPropertiesFamilyEventArgs> o)
  {
    onObjectPropertiesFamily.addObserver(o);
  }
  private EventObservable<AvatarUpdateEventArgs> onAvatarUpdate = new EventObservable<AvatarUpdateEventArgs>();
  public void registerOnAvatarUpdate(EventObserver<AvatarUpdateEventArgs> o)
  {
    onAvatarUpdate.addObserver(o);
  }
  public void unregisterOnAvatarUpdate(EventObserver<AvatarUpdateEventArgs> o)
  {
    onAvatarUpdate.addObserver(o);
  }
  private EventObservable<TerseObjectUpdateEventArgs> onTerseObjectUpdate = new EventObservable<TerseObjectUpdateEventArgs>();
  public void registerOnTerseObjectUpdate(EventObserver<TerseObjectUpdateEventArgs> o)
  {
    onTerseObjectUpdate.addObserver(o);
  }
  public void unregisterOnTerseObjectUpdate(EventObserver<TerseObjectUpdateEventArgs> o)
  {
    onTerseObjectUpdate.addObserver(o);
  }
  private EventObservable<ObjectDataBlockUpdateEventArgs> onObjectDataBlockUpdate = new EventObservable<ObjectDataBlockUpdateEventArgs>();
  public void registerOnObjectDataBlockUpdate(EventObserver<ObjectDataBlockUpdateEventArgs> o)
  {
    onObjectDataBlockUpdate.addObserver(o);
  }
  public void unregisterOnObjectDataBlockUpdate(EventObserver<ObjectDataBlockUpdateEventArgs> o)
  {
    onObjectDataBlockUpdate.addObserver(o);
  }
  private EventObservable<KillObjectEventArgs> onKillObject = new EventObservable<KillObjectEventArgs>();
  public void registerOnKillObject(EventObserver<KillObjectEventArgs> o)
  {
    onKillObject.addObserver(o);
  }
  public void unregisterOnKillObject(EventObserver<KillObjectEventArgs> o)
  {
    onKillObject.addObserver(o);
  }
  private EventObservable<AvatarSitChangedEventArgs> onAvatarSitChanged = new EventObservable<AvatarSitChangedEventArgs>();
  public void registerOnAvatarSitChanged(EventObserver<AvatarSitChangedEventArgs> o)
  {
    onAvatarSitChanged.addObserver(o);
  }
  public void unregisterOnAvatarSitChanged(EventObserver<AvatarSitChangedEventArgs> o)
  {
    onAvatarSitChanged.addObserver(o);
  }
  private EventObservable<PayPriceReplyEventArgs> onPayPriceReply = new EventObservable<PayPriceReplyEventArgs>();
  public void registerOnPayPriceReply(EventObserver<PayPriceReplyEventArgs> o)
  {
    onPayPriceReply.addObserver(o);
  }
  public void unregisterOnPayPriceReply(EventObserver<PayPriceReplyEventArgs> o)
  {
    onPayPriceReply.addObserver(o);
  }
  private EventObservable<PhysicsPropertiesEventArgs> onPhysicsProperties = new EventObservable<PhysicsPropertiesEventArgs>();
  public void registerOnPhysicsProperties(EventObserver<PhysicsPropertiesEventArgs> o)
  {
    onPhysicsProperties.addObserver(o);
  }
  public void unregisterOnPhysicsProperties(EventObserver<PhysicsPropertiesEventArgs> o)
  {
    onPhysicsProperties.addObserver(o);
  }



  //          /// <summary>The event subscribers, null of no subscribers</summary>
  //          private EventHandler<PrimEventArgs> m_ObjectUpdate;
  //
  //          /// <summary>Thread sync lock object</summary>
  //          private readonly object m_ObjectUpdateLock = new object();
  //
  //          /// <summary>Raised when the simulator sends us data containing
  //          /// A <see cref="Primitive"/>, Foliage or Attachment</summary>
  //          /// <seealso cref="RequestObject"/>
  //          /// <seealso cref="RequestObjects"/>
  //          public event EventHandler<PrimEventArgs> ObjectUpdate
  //          {
  //              add { lock (m_ObjectUpdateLock) { m_ObjectUpdate += value; } }
  //              remove { lock (m_ObjectUpdateLock) { m_ObjectUpdate -= value; } }
  //          }
  //
  //          /// <summary>The event subscribers, null of no subscribers</summary>
  //          private EventHandler<ObjectPropertiesEventArgs> m_ObjectProperties;
  //
  //          ///<summary>Raises the ObjectProperties Event</summary>
  //          /// <param name="e">A ObjectPropertiesEventArgs object containing
  //          /// the data sent from the simulator</param>
  //          protected virtual void OnObjectProperties(ObjectPropertiesEventArgs e)
  //          {
  //              EventHandler<ObjectPropertiesEventArgs> handler = m_ObjectProperties;
  //              if (handler != null)
  //                  handler(this, e);
  //          }
  //
  //          /// <summary>Thread sync lock object</summary>
  //          private readonly object m_ObjectPropertiesLock = new object();
  //
  //          /// <summary>Raised when the simulator sends us data containing
  //          /// additional <seea cref="Primitive"/> information</summary>
  //          /// <seealso cref="SelectObject"/>
  //          /// <seealso cref="SelectObjects"/>
  //          public event EventHandler<ObjectPropertiesEventArgs> ObjectProperties
  //          {
  //              add { lock (m_ObjectPropertiesLock) { m_ObjectProperties += value; } }
  //              remove { lock (m_ObjectPropertiesLock) { m_ObjectProperties -= value; } }
  //          }
  //
  //          /// <summary>The event subscribers, null of no subscribers</summary>
  //          private EventHandler<ObjectPropertiesUpdatedEventArgs> m_ObjectPropertiesUpdated;
  //
  //          ///<summary>Raises the ObjectPropertiesUpdated Event</summary>
  //          /// <param name="e">A ObjectPropertiesUpdatedEventArgs object containing
  //          /// the data sent from the simulator</param>
  //          protected virtual void OnObjectPropertiesUpdated(ObjectPropertiesUpdatedEventArgs e)
  //          {
  //              EventHandler<ObjectPropertiesUpdatedEventArgs> handler = m_ObjectPropertiesUpdated;
  //              if (handler != null)
  //                  handler(this, e);
  //          }
  //
  //          /// <summary>Thread sync lock object</summary>
  //          private readonly object m_ObjectPropertiesUpdatedLock = new object();
  //
  //          /// <summary>Raised when the simulator sends us data containing
  //          /// Primitive.ObjectProperties for an object we are currently tracking</summary>
  //          public event EventHandler<ObjectPropertiesUpdatedEventArgs> ObjectPropertiesUpdated
  //          {
  //              add { lock (m_ObjectPropertiesUpdatedLock) { m_ObjectPropertiesUpdated += value; } }
  //              remove { lock (m_ObjectPropertiesUpdatedLock) { m_ObjectPropertiesUpdated -= value; } }
  //          }
  //
  //          /// <summary>The event subscribers, null of no subscribers</summary>
  //          private EventHandler<ObjectPropertiesFamilyEventArgs> m_ObjectPropertiesFamily;
  //
  //          ///<summary>Raises the ObjectPropertiesFamily Event</summary>
  //          /// <param name="e">A ObjectPropertiesFamilyEventArgs object containing
  //          /// the data sent from the simulator</param>
  //          protected virtual void OnObjectPropertiesFamily(ObjectPropertiesFamilyEventArgs e)
  //          {
  //              EventHandler<ObjectPropertiesFamilyEventArgs> handler = m_ObjectPropertiesFamily;
  //              if (handler != null)
  //                  handler(this, e);
  //          }
  //
  //          /// <summary>Thread sync lock object</summary>
  //          private readonly object m_ObjectPropertiesFamilyLock = new object();
  //
  //          /// <summary>Raised when the simulator sends us data containing
  //          /// additional <seea cref="Primitive"/> and <see cref="Avatar"/> details</summary>
  //          /// <seealso cref="RequestObjectPropertiesFamily"/>
  //          public event EventHandler<ObjectPropertiesFamilyEventArgs> ObjectPropertiesFamily
  //          {
  //              add { lock (m_ObjectPropertiesFamilyLock) { m_ObjectPropertiesFamily += value; } }
  //              remove { lock (m_ObjectPropertiesFamilyLock) { m_ObjectPropertiesFamily -= value; } }
  //          }
  //
  //          /// <summary>The event subscribers, null of no subscribers</summary>
  //          private EventHandler<AvatarUpdateEventArgs> m_AvatarUpdate;
  //
  //          ///<summary>Raises the AvatarUpdate Event</summary>
  //          /// <param name="e">A AvatarUpdateEventArgs object containing
  //          /// the data sent from the simulator</param>
  //          protected virtual void OnAvatarUpdate(AvatarUpdateEventArgs e)
  //          {
  //              EventHandler<AvatarUpdateEventArgs> handler = m_AvatarUpdate;
  //              if (handler != null)
  //                  handler(this, e);
  //          }
  //
  //          /// <summary>Thread sync lock object</summary>
  //          private readonly object m_AvatarUpdateLock = new object();
  //
  //          /// <summary>Raised when the simulator sends us data containing
  //          /// updated information for an <see cref="Avatar"/></summary>
  //          public event EventHandler<AvatarUpdateEventArgs> AvatarUpdate
  //          {
  //              add { lock (m_AvatarUpdateLock) { m_AvatarUpdate += value; } }
  //              remove { lock (m_AvatarUpdateLock) { m_AvatarUpdate -= value; } }
  //          }
  //
  //          /// <summary>The event subscribers, null of no subscribers</summary>
  //          private EventHandler<TerseObjectUpdateEventArgs> m_TerseObjectUpdate;
  //
  //          /// <summary>Thread sync lock object</summary>
  //          private readonly object m_TerseObjectUpdateLock = new object();
  //
  //          /// <summary>Raised when the simulator sends us data containing
  //          /// <see cref="Primitive"/> and <see cref="Avatar"/> movement changes</summary>
  //          public event EventHandler<TerseObjectUpdateEventArgs> TerseObjectUpdate
  //          {
  //              add { lock (m_TerseObjectUpdateLock) { m_TerseObjectUpdate += value; } }
  //              remove { lock (m_TerseObjectUpdateLock) { m_TerseObjectUpdate -= value; } }
  //          }
  //
  //          /// <summary>The event subscribers, null of no subscribers</summary>
  //          private EventHandler<ObjectDataBlockUpdateEventArgs> m_ObjectDataBlockUpdate;
  //
  //          ///<summary>Raises the ObjectDataBlockUpdate Event</summary>
  //          /// <param name="e">A ObjectDataBlockUpdateEventArgs object containing
  //          /// the data sent from the simulator</param>
  //          protected virtual void OnObjectDataBlockUpdate(ObjectDataBlockUpdateEventArgs e)
  //          {
  //              EventHandler<ObjectDataBlockUpdateEventArgs> handler = m_ObjectDataBlockUpdate;
  //              if (handler != null)
  //                  handler(this, e);
  //          }
  //
  //          /// <summary>Thread sync lock object</summary>
  //          private readonly object m_ObjectDataBlockUpdateLock = new object();
  //
  //          /// <summary>Raised when the simulator sends us data containing
  //          /// updates to an Objects DataBlock</summary>
  //          public event EventHandler<ObjectDataBlockUpdateEventArgs> ObjectDataBlockUpdate
  //          {
  //              add { lock (m_ObjectDataBlockUpdateLock) { m_ObjectDataBlockUpdate += value; } }
  //              remove { lock (m_ObjectDataBlockUpdateLock) { m_ObjectDataBlockUpdate -= value; } }
  //          }
  //
  //          /// <summary>The event subscribers, null of no subscribers</summary>
  //          private EventHandler<KillObjectEventArgs> m_KillObject;
  //
  //          ///<summary>Raises the KillObject Event</summary>
  //          /// <param name="e">A KillObjectEventArgs object containing
  //          /// the data sent from the simulator</param>
  //          protected virtual void OnKillObject(KillObjectEventArgs e)
  //          {
  //              EventHandler<KillObjectEventArgs> handler = m_KillObject;
  //              if (handler != null)
  //                  handler(this, e);
  //          }
  //
  //          /// <summary>Thread sync lock object</summary>
  //          private readonly object m_KillObjectLock = new object();
  //
  //          /// <summary>Raised when the simulator informs us an <see cref="Primitive"/>
  //          /// or <see cref="Avatar"/> is no longer within view</summary>
  //          public event EventHandler<KillObjectEventArgs> KillObject
  //          {
  //              add { lock (m_KillObjectLock) { m_KillObject += value; } }
  //              remove { lock (m_KillObjectLock) { m_KillObject -= value; } }
  //          }
  //
  //          /// <summary>The event subscribers, null of no subscribers</summary>
  //          private EventHandler<AvatarSitChangedEventArgs> m_AvatarSitChanged;
  //
  //          ///<summary>Raises the AvatarSitChanged Event</summary>
  //          /// <param name="e">A AvatarSitChangedEventArgs object containing
  //          /// the data sent from the simulator</param>
  //          protected virtual void OnAvatarSitChanged(AvatarSitChangedEventArgs e)
  //          {
  //              EventHandler<AvatarSitChangedEventArgs> handler = m_AvatarSitChanged;
  //              if (handler != null)
  //                  handler(this, e);
  //          }
  //
  //          /// <summary>Thread sync lock object</summary>
  //          private readonly object m_AvatarSitChangedLock = new object();
  //
  //          /// <summary>Raised when the simulator sends us data containing
  //          /// updated sit information for our <see cref="Avatar"/></summary>
  //          public event EventHandler<AvatarSitChangedEventArgs> AvatarSitChanged
  //          {
  //              add { lock (m_AvatarSitChangedLock) { m_AvatarSitChanged += value; } }
  //              remove { lock (m_AvatarSitChangedLock) { m_AvatarSitChanged -= value; } }
  //          }
  //
  //          /// <summary>The event subscribers, null of no subscribers</summary>
  //          private EventHandler<PayPriceReplyEventArgs> m_PayPriceReply;
  //
  //          ///<summary>Raises the PayPriceReply Event</summary>
  //          /// <param name="e">A PayPriceReplyEventArgs object containing
  //          /// the data sent from the simulator</param>
  //          protected virtual void OnPayPriceReply(PayPriceReplyEventArgs e)
  //          {
  //              EventHandler<PayPriceReplyEventArgs> handler = m_PayPriceReply;
  //              if (handler != null)
  //                  handler(this, e);
  //          }
  //
  //          /// <summary>Thread sync lock object</summary>
  //          private readonly object m_PayPriceReplyLock = new object();
  //
  //          /// <summary>Raised when the simulator sends us data containing
  //          /// purchase price information for a <see cref="Primitive"/></summary>
  //          public event EventHandler<PayPriceReplyEventArgs> PayPriceReply
  //          {
  //              add { lock (m_PayPriceReplyLock) { m_PayPriceReply += value; } }
  //              remove { lock (m_PayPriceReplyLock) { m_PayPriceReply -= value; } }
  //          }
  //
  //          /// <summary>
  //          /// Callback for getting object media data via CAP
  //          /// </summary>
  //          /// <param name="success">Indicates if the operation was succesfull</param>
  //          /// <param name="version">Object media version String</param>
  //          /// <param name="faceMedia">Array indexed on prim face of media entry data</param>
  //          public delegate void ObjectMediaCallback(bool success, String version, MediaEntry[] faceMedia);
  //
  //          /// <summary>The event subscribers, null of no subscribers</summary>
  //          private EventHandler<PhysicsPropertiesEventArgs> m_PhysicsProperties;
  //
  //          ///<summary>Raises the PhysicsProperties Event</summary>
  //          /// <param name="e">A PhysicsPropertiesEventArgs object containing
  //          /// the data sent from the simulator</param>
  //          protected virtual void OnPhysicsProperties(PhysicsPropertiesEventArgs e)
  //          {
  //              EventHandler<PhysicsPropertiesEventArgs> handler = m_PhysicsProperties;
  //              if (handler != null)
  //                  handler(this, e);
  //          }
  //
  //          /// <summary>Thread sync lock object</summary>
  //          private readonly object m_PhysicsPropertiesLock = new object();
  //
  //          /// <summary>Raised when the simulator sends us data containing
  //          /// additional <seea cref="Primitive"/> information</summary>
  //          /// <seealso cref="SelectObject"/>
  //          /// <seealso cref="SelectObjects"/>
  //          public event EventHandler<PhysicsPropertiesEventArgs> PhysicsProperties
  //          {
  //              add { lock (m_PhysicsPropertiesLock) { m_PhysicsProperties += value; } }
  //              remove { lock (m_PhysicsPropertiesLock) { m_PhysicsProperties -= value; } }
  //          }

  //endregion Delegates

  private static ThreadPool threadPool = ThreadPoolFactory.getThreadPool();

  /// <summary>Reference to the GridClient object</summary>
  protected GridClient Client;
  /// <summary>Does periodic dead reckoning calculation to convert
  /// velocity and acceleration to new positions for objects</summary>
  private Timer InterpolationTimer;

  /// <summary>
  /// Construct a new instance of the ObjectManager class
  /// </summary>
  /// <param name="client">A reference to the <see cref="GridClient"/> instance</param>
  public ObjectManager(GridClient client)
  {
    Client = client;

    //                  Client.network.RegisterCallback(PacketType.ObjectUpdate, ObjectUpdateHandler, false);
    Client.network.RegisterCallback(PacketType.ObjectUpdate, new EventObserver<PacketReceivedEventArgs>()
        {
      @Override
      public void handleEvent(Observable o,PacketReceivedEventArgs arg) {
        try{ ObjectUpdateHandler(o, arg);}
        catch(Exception e) {JLogger.warn(Utils.getExceptionStackTraceAsString(e));}
      }}, false
        );
    //              Client.network.RegisterCallback(PacketType.ImprovedTerseObjectUpdate, ImprovedTerseObjectUpdateHandler, false);
    Client.network.RegisterCallback(PacketType.ImprovedTerseObjectUpdate, new EventObserver<PacketReceivedEventArgs>()
        {
      @Override
      public void handleEvent(Observable o,PacketReceivedEventArgs arg) {
        try{ ImprovedTerseObjectUpdateHandler(o, arg);}
        catch(Exception e) {JLogger.warn(Utils.getExceptionStackTraceAsString(e));}
      }}, false
        );
    //              Client.network.RegisterCallback(PacketType.ObjectUpdateCompressed, ObjectUpdateCompressedHandler);
    Client.network.RegisterCallback(PacketType.ObjectUpdateCompressed, new EventObserver<PacketReceivedEventArgs>()
        {
      @Override
      public void handleEvent(Observable o,PacketReceivedEventArgs arg) {
        try{ ObjectUpdateCompressedHandler(o, arg);}
        catch(Exception e) {JLogger.warn(Utils.getExceptionStackTraceAsString(e));}
      }}
        );
    //              Client.network.RegisterCallback(PacketType.ObjectUpdateCached, ObjectUpdateCachedHandler);
    Client.network.RegisterCallback(PacketType.ObjectUpdateCached, new EventObserver<PacketReceivedEventArgs>()
        {
      @Override
      public void handleEvent(Observable o,PacketReceivedEventArgs arg) {
        try{ ObjectUpdateCachedHandler(o, arg);}
        catch(Exception e) {JLogger.warn(Utils.getExceptionStackTraceAsString(e));}
      }}
        );
    //              Client.network.RegisterCallback(PacketType.KillObject, KillObjectHandler);
    Client.network.RegisterCallback(PacketType.KillObject, new EventObserver<PacketReceivedEventArgs>()
        {
      @Override
      public void handleEvent(Observable o,PacketReceivedEventArgs arg) {
        try{ KillObjectHandler(o, arg);}
        catch(Exception e) {JLogger.warn(Utils.getExceptionStackTraceAsString(e));}
      }}
        );
    //              Client.network.RegisterCallback(PacketType.ObjectPropertiesFamily, ObjectPropertiesFamilyHandler);
    Client.network.RegisterCallback(PacketType.ObjectPropertiesFamily, new EventObserver<PacketReceivedEventArgs>()
        {
      @Override
      public void handleEvent(Observable o,PacketReceivedEventArgs arg) {
        try{ ObjectPropertiesFamilyHandler(o, arg);}
        catch(Exception e) {JLogger.warn(Utils.getExceptionStackTraceAsString(e));}
      }}
        );
    //              Client.network.RegisterCallback(PacketType.ObjectProperties, ObjectPropertiesHandler);
    Client.network.RegisterCallback(PacketType.ObjectProperties, new EventObserver<PacketReceivedEventArgs>()
        {
      @Override
      public void handleEvent(Observable o,PacketReceivedEventArgs arg) {
        try{ ObjectPropertiesHandler(o, arg);}
        catch(Exception e) {JLogger.warn(Utils.getExceptionStackTraceAsString(e));}
      }}
        );
    //              Client.network.RegisterCallback(PacketType.PayPriceReply, PayPriceReplyHandler);
    Client.network.RegisterCallback(PacketType.PayPriceReply, new EventObserver<PacketReceivedEventArgs>()
        {
      @Override
      public void handleEvent(Observable o,PacketReceivedEventArgs arg) {
        try{ PayPriceReplyHandler(o, arg);}
        catch(Exception e) {JLogger.warn(Utils.getExceptionStackTraceAsString(e));}
      }}
        );
    //              Client.network.RegisterEventCallback("ObjectPhysicsProperties", ObjectPhysicsPropertiesHandler);
    Client.network.RegisterEventCallback("ObjectPhysicsProperties",
        new EventObserver<CapsEventObservableArg>()
        {
      @Override
      public void handleEvent(Observable o,
          CapsEventObservableArg arg) {
        try{ObjectPhysicsPropertiesHandler(arg.getCapsKey(), arg.getMessage(), arg.getSimulator());}
        catch(Exception e) {JLogger.warn(Utils.getExceptionStackTraceAsString(e));}

      }});
  }

  //region Internal event handlers

  private void Network_OnDisconnected(NetworkManager.DisconnectType reason, String message)
  {
    if (InterpolationTimer != null)
    {
      InterpolationTimer.cancel();
      InterpolationTimer = null;
    }
  }

  private void Network_OnConnected(Object sender)
  {
    if (Client.settings.USE_INTERPOLATION_TIMER)
    {
      //                  InterpolationTimer = new Timer(InterpolationTimer_Elapsed, null, Settings.INTERPOLATION_INTERVAL, Timeout.Infinite);
      //      InterpolationTimer = new Timer();
      //      InterpolationTimer.schedule(new TimerTask(){
      //        @Override
      //        public void run() {
      //          InterpolationTimer_Elapsed(null);
      //        }       
      //      }, Settings.SIMULATOR_TIMEOUT);
      createInterpolationTimer(Client.settings.SIMULATOR_TIMEOUT);
    }
  }

  private void createInterpolationTimer(long delay)
  {
    InterpolationTimer = new Timer();
    InterpolationTimer.schedule(new TimerTask(){
      @Override
      public void run() {
        InterpolationTimer_Elapsed(null);
      }       
    }, delay);
  }

  private void cancelInterpolationTimer()
  {
    InterpolationTimer.cancel();
    InterpolationTimer = null;
  }


  private void updateInterpolationTimer(long delay)
  {
    cancelInterpolationTimer();
    createInterpolationTimer(delay);
  }


  //endregion Internal event handlers

  //region Public Methods

  /// <summary>
  /// Request information for a single object from a <see cref="Simulator"/>
  /// you are currently connected to
  /// </summary>
  /// <param name="simulator">The <see cref="Simulator"/> the object is located</param>
  /// <param name="localID">The Local ID of the object</param>
  public void RequestObject(Simulator simulator, long localID)
  {
    RequestMultipleObjectsPacket request = new RequestMultipleObjectsPacket();
    request.AgentData.AgentID = Client.self.getAgentID();
    request.AgentData.SessionID = Client.self.getSessionID();
    request.ObjectData = new RequestMultipleObjectsPacket.ObjectDataBlock[1];
    request.ObjectData[0] = new RequestMultipleObjectsPacket.ObjectDataBlock();
    request.ObjectData[0].ID = localID;
    request.ObjectData[0].CacheMissType = 0;

    Client.network.SendPacket(request, simulator);
  }

  /// <summary>
  /// Request information for multiple objects contained in
  /// the same simulator
  /// </summary>
  /// <param name="simulator">The <see cref="Simulator"/> the objects are located</param>
  /// <param name="localIDs">An array containing the Local IDs of the objects</param>
  public void RequestObjects(Simulator simulator, List<Long> localIDs)
  {
    RequestMultipleObjectsPacket request = new RequestMultipleObjectsPacket();
    request.AgentData.AgentID = Client.self.getAgentID();
    request.AgentData.SessionID = Client.self.getSessionID();
    request.ObjectData = new RequestMultipleObjectsPacket.ObjectDataBlock[localIDs.size()];

    for (int i = 0; i < localIDs.size(); i++)
    {
      request.ObjectData[i] = new RequestMultipleObjectsPacket.ObjectDataBlock();
      request.ObjectData[i].ID = localIDs.get(i);
      request.ObjectData[i].CacheMissType = 0;
    }

    Client.network.SendPacket(request, simulator);
  }

  /// <summary>
  /// Attempt to purchase an original object, a copy, or the contents of
  /// an object
  /// </summary>
  /// <param name="simulator">The <see cref="Simulator"/> the object is located</param>       
  /// <param name="localID">The Local ID of the object</param>
  /// <param name="saleType">Whether the original, a copy, or the object
  /// contents are on sale. This is used for verification, if the this
  /// sale type is not valid for the object the purchase will fail</param>
  /// <param name="price">Price of the object. This is used for
  /// verification, if it does not match the actual price the purchase
  /// will fail</param>
  /// <param name="groupID">Group ID that will be associated with the new
  /// purchase</param>
  /// <param name="categoryID">Inventory folder UUID where the object or objects
  /// purchased should be placed</param>
  /// <example>
  /// <code>
  ///     BuyObject(Client.network.CurrentSim, 500, SaleType.Copy,
  ///         100, UUID.Zero, Client.Self.InventoryRootFolderUUID);
  /// </code>
  ///</example>
  public void BuyObject(Simulator simulator, long localID, SaleType saleType, int price, UUID groupID,
      UUID categoryID)
  {
    ObjectBuyPacket buy = new ObjectBuyPacket();

    buy.AgentData.AgentID = Client.self.getAgentID();
    buy.AgentData.SessionID = Client.self.getSessionID();
    buy.AgentData.GroupID = groupID;
    buy.AgentData.CategoryID = categoryID;

    buy.ObjectData = new ObjectBuyPacket.ObjectDataBlock[1];
    buy.ObjectData[0] = new ObjectBuyPacket.ObjectDataBlock();
    buy.ObjectData[0].ObjectLocalID = localID;
    buy.ObjectData[0].SaleType = saleType.getIndex();
    buy.ObjectData[0].SalePrice = price;

    Client.network.SendPacket(buy, simulator);
  }

  /// <summary>
  /// Request prices that should be displayed in pay dialog. This will triggger the simulator
  /// to send us back a PayPriceReply which can be handled by OnPayPriceReply event
  /// </summary>
  /// <param name="simulator">The <see cref="Simulator"/> the object is located</param>
  /// <param name="objectID">The ID of the object</param>
  /// <remarks>The result is raised in the <see cref="PayPriceReply"/> event</remarks>
  public void RequestPayPrice(Simulator simulator, UUID objectID)
  {
    RequestPayPricePacket payPriceRequest = new RequestPayPricePacket();

    payPriceRequest.ObjectData = new RequestPayPricePacket.ObjectDataBlock();
    payPriceRequest.ObjectData.ObjectID = objectID;

    Client.network.SendPacket(payPriceRequest, simulator);
  }

  /// <summary>
  /// Select a single object. This will cause the <see cref="Simulator"/> to send us
  /// an <see cref="ObjectPropertiesPacket"/> which will raise the <see cref="ObjectProperties"/> event
  /// </summary>
  /// <param name="simulator">The <see cref="Simulator"/> the object is located</param>       
  /// <param name="localID">The Local ID of the object</param>       
  /// <seealso cref="ObjectPropertiesFamilyEventArgs"/>
  public void SelectObject(Simulator simulator, long localID)
  {
    SelectObject(simulator, localID, true);
  }

  /// <summary>
  /// Select a single object. This will cause the <see cref="Simulator"/> to send us
  /// an <see cref="ObjectPropertiesPacket"/> which will raise the <see cref="ObjectProperties"/> event
  /// </summary>
  /// <param name="simulator">The <see cref="Simulator"/> the object is located</param>
  /// <param name="localID">The Local ID of the object</param>
  /// <param name="automaticDeselect">if true, a call to <see cref="DeselectObject"/> is
  /// made immediately following the request</param>
  /// <seealso cref="ObjectPropertiesFamilyEventArgs"/>
  public void SelectObject(Simulator simulator, long localID, boolean automaticDeselect)
  {
    ObjectSelectPacket select = new ObjectSelectPacket();

    select.AgentData.AgentID = Client.self.getAgentID();
    select.AgentData.SessionID = Client.self.getSessionID();

    select.ObjectData = new ObjectSelectPacket.ObjectDataBlock[1];
    select.ObjectData[0] = new ObjectSelectPacket.ObjectDataBlock();
    select.ObjectData[0].ObjectLocalID = localID;

    Client.network.SendPacket(select, simulator);

    if (automaticDeselect)
    {
      DeselectObject(simulator, localID);
    }
  }

  /// <summary>
  /// Select multiple objects. This will cause the <see cref="Simulator"/> to send us
  /// an <see cref="ObjectPropertiesPacket"/> which will raise the <see cref="ObjectProperties"/> event
  /// </summary>       
  /// <param name="simulator">The <see cref="Simulator"/> the objects are located</param>
  /// <param name="localIDs">An array containing the Local IDs of the objects</param>
  /// <param name="automaticDeselect">Should objects be deselected immediately after selection</param>
  /// <seealso cref="ObjectPropertiesFamilyEventArgs"/>
  public void SelectObjects(Simulator simulator, long[] localIDs, boolean automaticDeselect)
  {
    ObjectSelectPacket select = new ObjectSelectPacket();

    select.AgentData.AgentID = Client.self.getAgentID();
    select.AgentData.SessionID = Client.self.getSessionID();

    select.ObjectData = new ObjectSelectPacket.ObjectDataBlock[localIDs.length];

    for (int i = 0; i < localIDs.length; i++)
    {
      select.ObjectData[i] = new ObjectSelectPacket.ObjectDataBlock();
      select.ObjectData[i].ObjectLocalID = localIDs[i];
    }

    Client.network.SendPacket(select, simulator);

    if (automaticDeselect)
    {
      DeselectObjects(simulator, localIDs);
    }
  }

  /// <summary>
  /// Select multiple objects. This will cause the <see cref="Simulator"/> to send us
  /// an <see cref="ObjectPropertiesPacket"/> which will raise the <see cref="ObjectProperties"/> event
  /// </summary>       
  /// <param name="simulator">The <see cref="Simulator"/> the objects are located</param>
  /// <param name="localIDs">An array containing the Local IDs of the objects</param>
  /// <seealso cref="ObjectPropertiesFamilyEventArgs"/>
  public void SelectObjects(Simulator simulator, long[] localIDs)
  {
    SelectObjects(simulator, localIDs, true);
  }

  /// <summary>
  /// Update the properties of an object
  /// </summary>
  /// <param name="simulator">The <see cref="Simulator"/> the object is located</param>       
  /// <param name="localID">The Local ID of the object</param>       
  /// <param name="physical">true to turn the objects physical property on</param>
  /// <param name="temporary">true to turn the objects temporary property on</param>
  /// <param name="phantom">true to turn the objects phantom property on</param>
  /// <param name="castsShadow">true to turn the objects cast shadows property on</param>
  public void SetFlags(Simulator simulator, long localID, boolean physical, boolean temporary, boolean phantom, boolean castsShadow)
  {
    SetFlags(simulator, localID, physical, temporary, phantom, castsShadow, PhysicsShapeType.Prim, 1000f, 0.6f, 0.5f, 1f);
  }

  /// <summary>
  /// Update the properties of an object
  /// </summary>
  /// <param name="simulator">The <see cref="Simulator"/> the object is located</param>       
  /// <param name="localID">The Local ID of the object</param>       
  /// <param name="physical">true to turn the objects physical property on</param>
  /// <param name="temporary">true to turn the objects temporary property on</param>
  /// <param name="phantom">true to turn the objects phantom property on</param>
  /// <param name="castsShadow">true to turn the objects cast shadows property on</param>
  /// <param name="physicsType">Type of the represetnation prim will have in the physics engine</param>
  /// <param name="density">Density - normal value 1000</param>
  /// <param name="friction">Friction - normal value 0.6</param>
  /// <param name="restitution">Restitution - standard value 0.5</param>
  /// <param name="gravityMultiplier">Gravity multiplier - standar value 1.0</param>
  public void SetFlags(Simulator simulator, long localID, boolean physical, boolean temporary, boolean phantom, boolean castsShadow,
      PhysicsShapeType physicsType, float density, float friction, float restitution, float gravityMultiplier)
  {
    ObjectFlagUpdatePacket flags = new ObjectFlagUpdatePacket();
    flags.AgentData.AgentID = Client.self.getAgentID();
    flags.AgentData.SessionID = Client.self.getSessionID();
    flags.AgentData.ObjectLocalID = localID;
    flags.AgentData.UsePhysics = physical;
    flags.AgentData.IsTemporary = temporary;
    flags.AgentData.IsPhantom = phantom;
    flags.AgentData.CastsShadows = castsShadow;

    flags.ExtraPhysics = new ObjectFlagUpdatePacket.ExtraPhysicsBlock[1];
    flags.ExtraPhysics[0] = new ObjectFlagUpdatePacket.ExtraPhysicsBlock();
    flags.ExtraPhysics[0].PhysicsShapeType = (byte)physicsType.getIndex();
    flags.ExtraPhysics[0].Density = density;
    flags.ExtraPhysics[0].Friction = friction;
    flags.ExtraPhysics[0].Restitution = restitution;
    flags.ExtraPhysics[0].GravityMultiplier = gravityMultiplier;

    Client.network.SendPacket(flags, simulator);
  }

  /// <summary>
  /// Sets the sale properties of a single object
  /// </summary>
  /// <param name="simulator">The <see cref="Simulator"/> the object is located</param>       
  /// <param name="localID">The Local ID of the object</param>       
  /// <param name="saleType">One of the options from the <see cref="SaleType"/> enum</param>
  /// <param name="price">The price of the object</param>
  public void SetSaleInfo(Simulator simulator, long localID, SaleType saleType, int price)
  {
    ObjectSaleInfoPacket sale = new ObjectSaleInfoPacket();
    sale.AgentData.AgentID = Client.self.getAgentID();
    sale.AgentData.SessionID = Client.self.getSessionID();
    sale.ObjectData = new ObjectSaleInfoPacket.ObjectDataBlock[1];
    sale.ObjectData[0] = new ObjectSaleInfoPacket.ObjectDataBlock();
    sale.ObjectData[0].LocalID = localID;
    sale.ObjectData[0].SalePrice = price;
    sale.ObjectData[0].SaleType = saleType.getIndex();

    Client.network.SendPacket(sale, simulator);
  }

  /// <summary>
  /// Sets the sale properties of multiple objects
  /// </summary>       
  /// <param name="simulator">The <see cref="Simulator"/> the objects are located</param>
  /// <param name="localIDs">An array containing the Local IDs of the objects</param>
  /// <param name="saleType">One of the options from the <see cref="SaleType"/> enum</param>
  /// <param name="price">The price of the object</param>
  public void SetSaleInfo(Simulator simulator, List<Long> localIDs, SaleType saleType, int price)
  {
    ObjectSaleInfoPacket sale = new ObjectSaleInfoPacket();
    sale.AgentData.AgentID = Client.self.getAgentID();
    sale.AgentData.SessionID = Client.self.getSessionID();
    sale.ObjectData = new ObjectSaleInfoPacket.ObjectDataBlock[localIDs.size()];

    for (int i = 0; i < localIDs.size(); i++)
    {
      sale.ObjectData[i] = new ObjectSaleInfoPacket.ObjectDataBlock();
      sale.ObjectData[i].LocalID = localIDs.get(i);
      sale.ObjectData[i].SalePrice = price;
      sale.ObjectData[i].SaleType = saleType.getIndex();
    }

    Client.network.SendPacket(sale, simulator);
  }

  /// <summary>
  /// Deselect a single object
  /// </summary>
  /// <param name="simulator">The <see cref="Simulator"/> the object is located</param>       
  /// <param name="localID">The Local ID of the object</param>
  public void DeselectObject(Simulator simulator, long localID)
  {
    ObjectDeselectPacket deselect = new ObjectDeselectPacket();

    deselect.AgentData.AgentID = Client.self.getAgentID();
    deselect.AgentData.SessionID = Client.self.getSessionID();

    deselect.ObjectData = new ObjectDeselectPacket.ObjectDataBlock[1];
    deselect.ObjectData[0] = new ObjectDeselectPacket.ObjectDataBlock();
    deselect.ObjectData[0].ObjectLocalID = localID;

    Client.network.SendPacket(deselect, simulator);
  }

  /// <summary>
  /// Deselect multiple objects.
  /// </summary>
  /// <param name="simulator">The <see cref="Simulator"/> the objects are located</param>
  /// <param name="localIDs">An array containing the Local IDs of the objects</param>
  public void DeselectObjects(Simulator simulator, long[] localIDs)
  {
    ObjectDeselectPacket deselect = new ObjectDeselectPacket();

    deselect.AgentData.AgentID = Client.self.getAgentID();
    deselect.AgentData.SessionID = Client.self.getSessionID();

    deselect.ObjectData = new ObjectDeselectPacket.ObjectDataBlock[localIDs.length];

    for (int i = 0; i < localIDs.length; i++)
    {
      deselect.ObjectData[i] = new ObjectDeselectPacket.ObjectDataBlock();
      deselect.ObjectData[i].ObjectLocalID = localIDs[i];
    }

    Client.network.SendPacket(deselect, simulator);
  }

  /// <summary>
  /// Perform a click action on an object
  /// </summary>
  /// <param name="simulator">The <see cref="Simulator"/> the object is located</param>       
  /// <param name="localID">The Local ID of the object</param>
  public void ClickObject(Simulator simulator, long localID) throws InterruptedException
  {
    ClickObject(simulator, localID, Vector3.Zero, Vector3.Zero, 0, Vector3.Zero, Vector3.Zero, Vector3.Zero);
  }

  /// <summary>
  /// Perform a click action (Grab) on a single object
  /// </summary>
  /// <param name="simulator">The <see cref="Simulator"/> the object is located</param>       
  /// <param name="localID">The Local ID of the object</param>
  /// <param name="uvCoord">The texture coordinates to touch</param>
  /// <param name="stCoord">The surface coordinates to touch</param>
  /// <param name="faceIndex">The face of the position to touch</param>
  /// <param name="position">The region coordinates of the position to touch</param>
  /// <param name="normal">The surface normal of the position to touch (A normal is a vector perpindicular to the surface)</param>
  /// <param name="binormal">The surface binormal of the position to touch (A binormal is a vector tangen to the surface
  /// pointing along the U direction of the tangent space</param>
  public void ClickObject(Simulator simulator, long localID, Vector3 uvCoord, Vector3 stCoord, int faceIndex, Vector3 position,
      Vector3 normal, Vector3 binormal) throws InterruptedException
      {
    ObjectGrabPacket grab = new ObjectGrabPacket();
    grab.AgentData.AgentID = Client.self.getAgentID();
    grab.AgentData.SessionID = Client.self.getSessionID();
    grab.ObjectData.GrabOffset = Vector3.Zero;
    grab.ObjectData.LocalID = localID;
    grab.SurfaceInfo = new ObjectGrabPacket.SurfaceInfoBlock[1];
    grab.SurfaceInfo[0] = new ObjectGrabPacket.SurfaceInfoBlock();
    grab.SurfaceInfo[0].UVCoord = uvCoord;
    grab.SurfaceInfo[0].STCoord = stCoord;
    grab.SurfaceInfo[0].FaceIndex = faceIndex;
    grab.SurfaceInfo[0].Position = position;
    grab.SurfaceInfo[0].Normal = normal;
    grab.SurfaceInfo[0].Binormal = binormal;

    Client.network.SendPacket(grab, simulator);

    // TODO: If these hit the server out of order the click will fail
    // and we'll be grabbing the object
    Thread.sleep(50);

    ObjectDeGrabPacket degrab = new ObjectDeGrabPacket();
    degrab.AgentData.AgentID = Client.self.getAgentID();
    degrab.AgentData.SessionID = Client.self.getSessionID();
    degrab.ObjectData.LocalID = localID;
    degrab.SurfaceInfo = new ObjectDeGrabPacket.SurfaceInfoBlock[1];
    degrab.SurfaceInfo[0] = new ObjectDeGrabPacket.SurfaceInfoBlock();
    degrab.SurfaceInfo[0].UVCoord = uvCoord;
    degrab.SurfaceInfo[0].STCoord = stCoord;
    degrab.SurfaceInfo[0].FaceIndex = faceIndex;
    degrab.SurfaceInfo[0].Position = position;
    degrab.SurfaceInfo[0].Normal = normal;
    degrab.SurfaceInfo[0].Binormal = binormal;

    Client.network.SendPacket(degrab, simulator);
      }

  /// <summary>
  /// Create (rez) a new prim object in a simulator
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object to place the object in</param>
  /// <param name="prim">Data describing the prim object to rez</param>
  /// <param name="groupID">Group ID that this prim will be set to, or UUID.Zero if you
  /// do not want the object to be associated with a specific group</param>
  /// <param name="position">An approximation of the position at which to rez the prim</param>
  /// <param name="scale">Scale vector to size this prim</param>
  /// <param name="rotation">Rotation quaternion to rotate this prim</param>
  /// <remarks>Due to the way client prim rezzing is done on the server,
  /// the requested position for an object is only close to where the prim
  /// actually ends up. If you desire exact placement you'll need to
  /// follow up by moving the object after it has been created. This
  /// function will not set textures, light and flexible data, or other
  /// extended primitive properties</remarks>
  public void AddPrim(Simulator simulator, ConstructionData prim, UUID groupID, Vector3 position,
      Vector3 scale, Quaternion rotation)
  {
    AddPrim(simulator, prim, groupID, position, scale, rotation, PrimFlags.CreateSelected);
  }

  /// <summary>
  /// Create (rez) a new prim object in a simulator
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="Simulator"/> object to place the object in</param>
  /// <param name="prim">Data describing the prim object to rez</param>
  /// <param name="groupID">Group ID that this prim will be set to, or UUID.Zero if you
  /// do not want the object to be associated with a specific group</param>
  /// <param name="position">An approximation of the position at which to rez the prim</param>
  /// <param name="scale">Scale vector to size this prim</param>
  /// <param name="rotation">Rotation quaternion to rotate this prim</param>
  /// <param name="createFlags">Specify the <seealso cref="PrimFlags"/></param>
  /// <remarks>Due to the way client prim rezzing is done on the server,
  /// the requested position for an object is only close to where the prim
  /// actually ends up. If you desire exact placement you'll need to
  /// follow up by moving the object after it has been created. This
  /// function will not set textures, light and flexible data, or other
  /// extended primitive properties</remarks>
  public void AddPrim(Simulator simulator, ConstructionData prim, UUID groupID, Vector3 position,
      Vector3 scale, Quaternion rotation, PrimFlags createFlags)
  {
    ObjectAddPacket packet = new ObjectAddPacket();

    packet.AgentData.AgentID = Client.self.getAgentID();
    packet.AgentData.SessionID = Client.self.getSessionID();
    packet.AgentData.GroupID = groupID;

    packet.ObjectData.State = prim.State;
    packet.ObjectData.AddFlags = (long)createFlags.getIndex();
    packet.ObjectData.PCode = (byte)PCode.Prim.getIndex();

    packet.ObjectData.Material = (byte)prim.Material.getIndex();
    packet.ObjectData.Scale = scale;
    packet.ObjectData.Rotation = rotation;

    packet.ObjectData.PathCurve = (byte)prim.PathCurve.getIndex();
    packet.ObjectData.PathBegin = Primitive.PackBeginCut(prim.PathBegin);
    packet.ObjectData.PathEnd = Primitive.PackEndCut(prim.PathEnd);
    packet.ObjectData.PathRadiusOffset = Primitive.PackPathTwist(prim.PathRadiusOffset);
    packet.ObjectData.PathRevolutions = Primitive.PackPathRevolutions(prim.PathRevolutions);
    packet.ObjectData.PathScaleX = Primitive.PackPathScale(prim.PathScaleX);
    packet.ObjectData.PathScaleY = Primitive.PackPathScale(prim.PathScaleY);
    packet.ObjectData.PathShearX = (byte)Primitive.PackPathShear(prim.PathShearX);
    packet.ObjectData.PathShearY = (byte)Primitive.PackPathShear(prim.PathShearY);
    packet.ObjectData.PathSkew = Primitive.PackPathTwist(prim.PathSkew);
    packet.ObjectData.PathTaperX = Primitive.PackPathTaper(prim.PathTaperX);
    packet.ObjectData.PathTaperY = Primitive.PackPathTaper(prim.PathTaperY);
    packet.ObjectData.PathTwist = Primitive.PackPathTwist(prim.PathTwist);
    packet.ObjectData.PathTwistBegin = Primitive.PackPathTwist(prim.PathTwistBegin);

    packet.ObjectData.ProfileCurve = prim.profileCurve;
    packet.ObjectData.ProfileBegin = Primitive.PackBeginCut(prim.ProfileBegin);
    packet.ObjectData.ProfileEnd = Primitive.PackEndCut(prim.ProfileEnd);
    packet.ObjectData.ProfileHollow = Primitive.PackProfileHollow(prim.ProfileHollow);

    packet.ObjectData.RayStart = position;
    packet.ObjectData.RayEnd = position;
    packet.ObjectData.RayEndIsIntersection = 0;
    packet.ObjectData.RayTargetID = UUID.Zero;
    packet.ObjectData.BypassRaycast = 1;

    Client.network.SendPacket(packet, simulator);
  }

  /// <summary>
  /// Rez a Linden tree
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="scale">The size of the tree</param>
  /// <param name="rotation">The rotation of the tree</param>
  /// <param name="position">The position of the tree</param>
  /// <param name="treeType">The Type of tree</param>
  /// <param name="groupOwner">The <seealso cref="UUID"/> of the group to set the tree to,
  /// or UUID.Zero if no group is to be set</param>
  /// <param name="newTree">true to use the "new" Linden trees, false to use the old</param>
  public void AddTree(Simulator simulator, Vector3 scale, Quaternion rotation, Vector3 position,
      Tree treeType, UUID groupOwner, boolean newTree)
  {
    ObjectAddPacket add = new ObjectAddPacket();

    add.AgentData.AgentID = Client.self.getAgentID();
    add.AgentData.SessionID = Client.self.getSessionID();
    add.AgentData.GroupID = groupOwner;
    add.ObjectData.BypassRaycast = 1;
    add.ObjectData.Material = 3;
    add.ObjectData.PathCurve = 16;
    add.ObjectData.PCode = newTree ? (byte)PCode.NewTree.getIndex() : (byte)PCode.Tree.getIndex();
    add.ObjectData.RayEnd = position;
    add.ObjectData.RayStart = position;
    add.ObjectData.RayTargetID = UUID.Zero;
    add.ObjectData.Rotation = rotation;
    add.ObjectData.Scale = scale;
    add.ObjectData.State = (byte)treeType.getIndex();

    Client.network.SendPacket(add, simulator);
  }

  /// <summary>
  /// Rez grass and ground cover
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="scale">The size of the grass</param>
  /// <param name="rotation">The rotation of the grass</param>
  /// <param name="position">The position of the grass</param>
  /// <param name="grassType">The type of grass from the <seealso cref="Grass"/> enum</param>
  /// <param name="groupOwner">The <seealso cref="UUID"/> of the group to set the tree to,
  /// or UUID.Zero if no group is to be set</param>
  public void AddGrass(Simulator simulator, Vector3 scale, Quaternion rotation, Vector3 position,
      Grass grassType, UUID groupOwner)
  {
    ObjectAddPacket add = new ObjectAddPacket();

    add.AgentData.AgentID = Client.self.getAgentID();
    add.AgentData.SessionID = Client.self.getSessionID();
    add.AgentData.GroupID = groupOwner;
    add.ObjectData.BypassRaycast = 1;
    add.ObjectData.Material = 3;
    add.ObjectData.PathCurve = 16;
    add.ObjectData.PCode = (byte)PCode.Grass.getIndex();
    add.ObjectData.RayEnd = position;
    add.ObjectData.RayStart = position;
    add.ObjectData.RayTargetID = UUID.Zero;
    add.ObjectData.Rotation = rotation;
    add.ObjectData.Scale = scale;
    add.ObjectData.State = (byte)grassType.getIndex();

    Client.network.SendPacket(add, simulator);
  }

  /// <summary>
  /// Set the textures to apply to the faces of an object
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="localID">The objects ID which is local to the simulator the object is in</param>
  /// <param name="textures">The texture data to apply</param>
  public void SetTextures(Simulator simulator, long localID, TextureEntry textures) throws IOException
  {
    SetTextures(simulator, localID, textures, "");
  }

  /// <summary>
  /// Set the textures to apply to the faces of an object
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="localID">The objects ID which is local to the simulator the object is in</param>
  /// <param name="textures">The texture data to apply</param>
  /// <param name="mediaUrl">A media URL (not used)</param>
  public void SetTextures(Simulator simulator, long localID, TextureEntry textures, String mediaUrl) throws IOException
  {
    ObjectImagePacket image = new ObjectImagePacket();

    image.AgentData.AgentID = Client.self.getAgentID();
    image.AgentData.SessionID = Client.self.getSessionID();
    image.ObjectData = new ObjectImagePacket.ObjectDataBlock[1];
    image.ObjectData[0] = new ObjectImagePacket.ObjectDataBlock();
    image.ObjectData[0].ObjectLocalID = localID;
    image.ObjectData[0].TextureEntry = textures.GetBytes();
    image.ObjectData[0].MediaURL = Utils.stringToBytesWithTrailingNullByte(mediaUrl);

    Client.network.SendPacket(image, simulator);
  }

  /// <summary>
  /// Set the Light data on an object
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="localID">The objects ID which is local to the simulator the object is in</param>
  /// <param name="light">A <seealso cref="LightData"/> object containing the data to set</param>
  public void SetLight(Simulator simulator, long localID, LightData light)
  {
    ObjectExtraParamsPacket extra = new ObjectExtraParamsPacket();

    extra.AgentData.AgentID = Client.self.getAgentID();
    extra.AgentData.SessionID = Client.self.getSessionID();
    extra.ObjectData = new ObjectExtraParamsPacket.ObjectDataBlock[1];
    extra.ObjectData[0] = new ObjectExtraParamsPacket.ObjectDataBlock();
    extra.ObjectData[0].ObjectLocalID = localID;
    extra.ObjectData[0].ParamType = ExtraParamType.Light.getIndex();
    if (light.Intensity == 0.0f)
    {
      // Disables the light if intensity is 0
      extra.ObjectData[0].ParamInUse = false;
    }
    else
    {
      extra.ObjectData[0].ParamInUse = true;
    }
    extra.ObjectData[0].ParamData = light.GetBytes();
    extra.ObjectData[0].ParamSize = (long)extra.ObjectData[0].ParamData.length;

    Client.network.SendPacket(extra, simulator);
  }

  /// <summary>
  /// Set the flexible data on an object
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="localID">The objects ID which is local to the simulator the object is in</param>
  /// <param name="flexible">A <seealso cref="Primitive.FlexibleData"/> object containing the data to set</param>
  public void SetFlexible(Simulator simulator, long localID,FlexibleData flexible)
  {
    ObjectExtraParamsPacket extra = new ObjectExtraParamsPacket();

    extra.AgentData.AgentID = Client.self.getAgentID();
    extra.AgentData.SessionID = Client.self.getSessionID();
    extra.ObjectData = new ObjectExtraParamsPacket.ObjectDataBlock[1];
    extra.ObjectData[0] = new ObjectExtraParamsPacket.ObjectDataBlock();
    extra.ObjectData[0].ObjectLocalID = localID;
    extra.ObjectData[0].ParamType = ExtraParamType.Flexible.getIndex();
    extra.ObjectData[0].ParamInUse = true;
    extra.ObjectData[0].ParamData = flexible.GetBytes();
    extra.ObjectData[0].ParamSize = (long)extra.ObjectData[0].ParamData.length;

    Client.network.SendPacket(extra, simulator);
  }

  /// <summary>
  /// Set the sculptie texture and data on an object
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="localID">The objects ID which is local to the simulator the object is in</param>
  /// <param name="sculpt">A <seealso cref="Primitive.SculptData"/> object containing the data to set</param>
  public void SetSculpt(Simulator simulator, long localID,SculptData sculpt)
  {
    ObjectExtraParamsPacket extra = new ObjectExtraParamsPacket();

    extra.AgentData.AgentID = Client.self.getAgentID();
    extra.AgentData.SessionID = Client.self.getSessionID();

    extra.ObjectData = new ObjectExtraParamsPacket.ObjectDataBlock[1];
    extra.ObjectData[0] = new ObjectExtraParamsPacket.ObjectDataBlock();
    extra.ObjectData[0].ObjectLocalID = localID;
    extra.ObjectData[0].ParamType = ExtraParamType.Sculpt.getIndex();
    extra.ObjectData[0].ParamInUse = true;
    extra.ObjectData[0].ParamData = sculpt.GetBytes();
    extra.ObjectData[0].ParamSize = (long)extra.ObjectData[0].ParamData.length;

    Client.network.SendPacket(extra, simulator);

    // Not sure why, but if you don't send this the sculpted prim disappears
    ObjectShapePacket shape = new ObjectShapePacket();

    shape.AgentData.AgentID = Client.self.getAgentID();
    shape.AgentData.SessionID = Client.self.getSessionID();

    shape.ObjectData = new ObjectShapePacket.ObjectDataBlock[1];
    shape.ObjectData[0] = new ObjectShapePacket.ObjectDataBlock();
    shape.ObjectData[0].ObjectLocalID = localID;
    shape.ObjectData[0].PathScaleX = 100;
    shape.ObjectData[0].PathScaleY = (byte)150;
    shape.ObjectData[0].PathCurve = 32;

    Client.network.SendPacket(shape, simulator);
  }

  /// <summary>
  /// Unset additional primitive parameters on an object
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="localID">The objects ID which is local to the simulator the object is in</param>
  /// <param name="type">The extra parameters to set</param>
  public void SetExtraParamOff(Simulator simulator, long localID, EnumSet<ExtraParamType> type)
  {
    ObjectExtraParamsPacket extra = new ObjectExtraParamsPacket();

    extra.AgentData.AgentID = Client.self.getAgentID();
    extra.AgentData.SessionID = Client.self.getSessionID();
    extra.ObjectData = new ObjectExtraParamsPacket.ObjectDataBlock[1];
    extra.ObjectData[0] = new ObjectExtraParamsPacket.ObjectDataBlock();
    extra.ObjectData[0].ObjectLocalID = localID;
    extra.ObjectData[0].ParamType = ExtraParamType.getIndex(type);
    extra.ObjectData[0].ParamInUse = false;
    extra.ObjectData[0].ParamData = Utils.EmptyBytes;
    extra.ObjectData[0].ParamSize = 0;

    Client.network.SendPacket(extra, simulator);
  }

  /// <summary>
  /// Link multiple prims into a linkset
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the objects reside</param>
  /// <param name="localIDs">An array which contains the IDs of the objects to link</param>
  /// <remarks>The last object in the array will be the root object of the linkset TODO: Is this true?</remarks>
  public void LinkPrims(Simulator simulator, List<Long> localIDs)
  {
    ObjectLinkPacket packet = new ObjectLinkPacket();

    packet.AgentData.AgentID = Client.self.getAgentID();
    packet.AgentData.SessionID = Client.self.getSessionID();

    packet.ObjectData = new ObjectLinkPacket.ObjectDataBlock[localIDs.size()];

    for (int i = 0; i < localIDs.size(); i++)
    {
      packet.ObjectData[i] = new ObjectLinkPacket.ObjectDataBlock();
      packet.ObjectData[i].ObjectLocalID = localIDs.get(i);
    }

    Client.network.SendPacket(packet, simulator);
  }

  /// <summary>
  /// Delink/Unlink multiple prims from a linkset
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the objects reside</param>
  /// <param name="localIDs">An array which contains the IDs of the objects to delink</param>
  public void DelinkPrims(Simulator simulator, List<Long> localIDs)
  {
    ObjectDelinkPacket packet = new ObjectDelinkPacket();

    packet.AgentData.AgentID = Client.self.getAgentID();
    packet.AgentData.SessionID = Client.self.getSessionID();

    packet.ObjectData = new ObjectDelinkPacket.ObjectDataBlock[localIDs.size()];

    int i = 0;
    for (long localID : localIDs)
    {
      packet.ObjectData[i] = new ObjectDelinkPacket.ObjectDataBlock();
      packet.ObjectData[i].ObjectLocalID = localID;

      i++;
    }

    Client.network.SendPacket(packet, simulator);
  }

  /// <summary>
  /// Change the rotation of an object
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="localID">The objects ID which is local to the simulator the object is in</param>
  /// <param name="rotation">The new rotation of the object</param>
  public void SetRotation(Simulator simulator, long localID, Quaternion rotation)
  {
    ObjectRotationPacket objRotPacket = new ObjectRotationPacket();
    objRotPacket.AgentData.AgentID = Client.self.getAgentID();
    objRotPacket.AgentData.SessionID = Client.self.getSessionID();

    objRotPacket.ObjectData = new ObjectRotationPacket.ObjectDataBlock[1];

    objRotPacket.ObjectData[0] = new ObjectRotationPacket.ObjectDataBlock();
    objRotPacket.ObjectData[0].ObjectLocalID = localID;
    objRotPacket.ObjectData[0].Rotation = rotation;
    Client.network.SendPacket(objRotPacket, simulator);
  }

  /// <summary>
  /// Set the name of an object
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="localID">The objects ID which is local to the simulator the object is in</param>
  /// <param name="name">A String containing the new name of the object</param>
  public void SetName(Simulator simulator, long localID, String name)
  {
    SetNames(simulator, new long[] { localID }, new String[] { name });
  }

  /// <summary>
  /// Set the name of multiple objects
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the objects reside</param>
  /// <param name="localIDs">An array which contains the IDs of the objects to change the name of</param>
  /// <param name="names">An array which contains the new names of the objects</param>
  public void SetNames(Simulator simulator, long[] localIDs, String[] names)
  {
    ObjectNamePacket namePacket = new ObjectNamePacket();
    namePacket.AgentData.AgentID = Client.self.getAgentID();
    namePacket.AgentData.SessionID = Client.self.getSessionID();

    namePacket.ObjectData = new ObjectNamePacket.ObjectDataBlock[localIDs.length];

    for (int i = 0; i < localIDs.length; ++i)
    {
      namePacket.ObjectData[i] = new ObjectNamePacket.ObjectDataBlock();
      namePacket.ObjectData[i].LocalID = localIDs[i];
      namePacket.ObjectData[i].Name = Utils.stringToBytesWithTrailingNullByte(names[i]);
    }

    Client.network.SendPacket(namePacket, simulator);
  }

  /// <summary>
  /// Set the description of an object
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="localID">The objects ID which is local to the simulator the object is in</param>
  /// <param name="description">A String containing the new description of the object</param>
  public void SetDescription(Simulator simulator, long localID, String description)
  {
    SetDescriptions(simulator, new long[] { localID }, new String[] { description });
  }

  /// <summary>
  /// Set the descriptions of multiple objects
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the objects reside</param>
  /// <param name="localIDs">An array which contains the IDs of the objects to change the description of</param>
  /// <param name="descriptions">An array which contains the new descriptions of the objects</param>
  public void SetDescriptions(Simulator simulator, long[] localIDs, String[] descriptions)
  {
    ObjectDescriptionPacket descPacket = new ObjectDescriptionPacket();
    descPacket.AgentData.AgentID = Client.self.getAgentID();
    descPacket.AgentData.SessionID = Client.self.getSessionID();

    descPacket.ObjectData = new ObjectDescriptionPacket.ObjectDataBlock[localIDs.length];

    for (int i = 0; i < localIDs.length; ++i)
    {
      descPacket.ObjectData[i] = new ObjectDescriptionPacket.ObjectDataBlock();
      descPacket.ObjectData[i].LocalID = localIDs[i];
      descPacket.ObjectData[i].Description = Utils.stringToBytesWithTrailingNullByte(descriptions[i]);
    }

    Client.network.SendPacket(descPacket, simulator);
  }

  /// <summary>
  /// Attach an object to this avatar
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="localID">The objects ID which is local to the simulator the object is in</param>
  /// <param name="attachPoint">The point on the avatar the object will be attached</param>
  /// <param name="rotation">The rotation of the attached object</param>
  public void AttachObject(Simulator simulator, long localID, AttachmentPoint attachPoint, Quaternion rotation)
  {
    ObjectAttachPacket attach = new ObjectAttachPacket();
    attach.AgentData.AgentID = Client.self.getAgentID();
    attach.AgentData.SessionID = Client.self.getSessionID();
    attach.AgentData.AttachmentPoint = attachPoint.getIndex();

    attach.ObjectData = new ObjectAttachPacket.ObjectDataBlock[1];
    attach.ObjectData[0] = new ObjectAttachPacket.ObjectDataBlock();
    attach.ObjectData[0].ObjectLocalID = localID;
    attach.ObjectData[0].Rotation = rotation;

    Client.network.SendPacket(attach, simulator);
  }

  /// <summary>
  /// Drop an attached object from this avatar
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/>
  /// object where the objects reside. This will always be the simulator the avatar is currently in
  /// </param>
  /// <param name="localID">The object's ID which is local to the simulator the object is in</param>
  public void DropObject(Simulator simulator, long localID)
  {
    ObjectDropPacket dropit = new ObjectDropPacket();
    dropit.AgentData.AgentID = Client.self.getAgentID();
    dropit.AgentData.SessionID = Client.self.getSessionID();
    dropit.ObjectData = new ObjectDropPacket.ObjectDataBlock[1];
    dropit.ObjectData[0] = new ObjectDropPacket.ObjectDataBlock();
    dropit.ObjectData[0].ObjectLocalID = localID;

    Client.network.SendPacket(dropit, simulator);
  }

  /// <summary>
  /// Detach an object from yourself
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/>
  /// object where the objects reside
  ///
  /// This will always be the simulator the avatar is currently in
  /// </param>
  /// <param name="localIDs">An array which contains the IDs of the objects to detach</param>
  public void DetachObjects(Simulator simulator, List<Long> localIDs)
  {
    ObjectDetachPacket detach = new ObjectDetachPacket();
    detach.AgentData.AgentID = Client.self.getAgentID();
    detach.AgentData.SessionID = Client.self.getSessionID();
    detach.ObjectData = new ObjectDetachPacket.ObjectDataBlock[localIDs.size()];

    for (int i = 0; i < localIDs.size(); i++)
    {
      detach.ObjectData[i] = new ObjectDetachPacket.ObjectDataBlock();
      detach.ObjectData[i].ObjectLocalID = localIDs.get(i);
    }

    Client.network.SendPacket(detach, simulator);
  }

  /// <summary>
  /// Change the position of an object, Will change position of entire linkset
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="localID">The objects ID which is local to the simulator the object is in</param>
  /// <param name="position">The new position of the object</param>
  public void SetPosition(Simulator simulator, long localID, Vector3 position)
  {
    UpdateObject(simulator, localID, position, UpdateType.get(UpdateType.Position.getIndex() | UpdateType.Linked.getIndex()));
  }

  /// <summary>
  /// Change the position of an object
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="localID">The objects ID which is local to the simulator the object is in</param>
  /// <param name="position">The new position of the object</param>
  /// <param name="childOnly">if true, will change position of (this) child prim only, not entire linkset</param>
  public void SetPosition(Simulator simulator, long localID, Vector3 position, boolean childOnly)
  {
    EnumSet<UpdateType> type = UpdateType.get(UpdateType.Position.getIndex());

    if (!childOnly)
      type = UpdateType.get(UpdateType.getIndex(type) | UpdateType.Linked.getIndex());

    UpdateObject(simulator, localID, position, type);
  }

  /// <summary>
  /// Change the Scale (size) of an object
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="localID">The objects ID which is local to the simulator the object is in</param>
  /// <param name="scale">The new scale of the object</param>
  /// <param name="childOnly">If true, will change scale of this prim only, not entire linkset</param>
  /// <param name="uniform">True to resize prims uniformly</param>
  public void SetScale(Simulator simulator, long localID, Vector3 scale, boolean childOnly, boolean uniform)
  {
    EnumSet<UpdateType> type = UpdateType.get(UpdateType.Scale.getIndex());

    if (!childOnly)
      type = UpdateType.get(UpdateType.getIndex(type) | UpdateType.Linked.getIndex());

    if (uniform)
      type = UpdateType.get(UpdateType.getIndex(type) | UpdateType.Uniform.getIndex());

    UpdateObject(simulator, localID, scale, type);
  }

  /// <summary>
  /// Change the Rotation of an object that is either a child or a whole linkset
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="localID">The objects ID which is local to the simulator the object is in</param>
  /// <param name="quat">The new scale of the object</param>
  /// <param name="childOnly">If true, will change rotation of this prim only, not entire linkset</param>
  public void SetRotation(Simulator simulator, long localID, Quaternion quat, boolean childOnly)
  {
    EnumSet<UpdateType> type = UpdateType.get(UpdateType.Rotation.getIndex());

    if (!childOnly)
      type = UpdateType.get(UpdateType.getIndex(type) | UpdateType.Linked.getIndex());

    MultipleObjectUpdatePacket multiObjectUpdate = new MultipleObjectUpdatePacket();
    multiObjectUpdate.AgentData.AgentID = Client.self.getAgentID();
    multiObjectUpdate.AgentData.SessionID = Client.self.getSessionID();

    multiObjectUpdate.ObjectData = new MultipleObjectUpdatePacket.ObjectDataBlock[1];

    multiObjectUpdate.ObjectData[0] = new MultipleObjectUpdatePacket.ObjectDataBlock();
    multiObjectUpdate.ObjectData[0].Type = (byte)UpdateType.getIndex(type);
    multiObjectUpdate.ObjectData[0].ObjectLocalID = localID;
    multiObjectUpdate.ObjectData[0].Data = quat.getBytes();

    Client.network.SendPacket(multiObjectUpdate, simulator);
  }

  /// <summary>
  /// Send a Multiple Object Update packet to change the size, scale or rotation of a primitive
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="localID">The objects ID which is local to the simulator the object is in</param>
  /// <param name="data">The new rotation, size, or position of the target object</param>
  /// <param name="type">The flags from the <seealso cref="UpdateType"/> Enum</param>
  public void UpdateObject(Simulator simulator, long localID, Vector3 data, EnumSet<UpdateType> type)
  {
    MultipleObjectUpdatePacket multiObjectUpdate = new MultipleObjectUpdatePacket();
    multiObjectUpdate.AgentData.AgentID = Client.self.getAgentID();
    multiObjectUpdate.AgentData.SessionID = Client.self.getSessionID();

    multiObjectUpdate.ObjectData = new MultipleObjectUpdatePacket.ObjectDataBlock[1];

    multiObjectUpdate.ObjectData[0] = new MultipleObjectUpdatePacket.ObjectDataBlock();
    multiObjectUpdate.ObjectData[0].Type = (byte)UpdateType.getIndex(type);
    multiObjectUpdate.ObjectData[0].ObjectLocalID = localID;
    multiObjectUpdate.ObjectData[0].Data = data.getBytes();

    Client.network.SendPacket(multiObjectUpdate, simulator);
  }

  /// <summary>
  /// Deed an object (prim) to a group, Object must be shared with group which
  /// can be accomplished with SetPermissions()
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="localID">The objects ID which is local to the simulator the object is in</param>
  /// <param name="groupOwner">The <seealso cref="UUID"/> of the group to deed the object to</param>
  public void DeedObject(Simulator simulator, long localID, UUID groupOwner)
  {
    ObjectOwnerPacket objDeedPacket = new ObjectOwnerPacket();
    objDeedPacket.AgentData.AgentID = Client.self.getAgentID();
    objDeedPacket.AgentData.SessionID = Client.self.getSessionID();

    // Can only be use in God mode
    objDeedPacket.HeaderData.Override = false;
    objDeedPacket.HeaderData.OwnerID = UUID.Zero;
    objDeedPacket.HeaderData.GroupID = groupOwner;

    objDeedPacket.ObjectData = new ObjectOwnerPacket.ObjectDataBlock[1];
    objDeedPacket.ObjectData[0] = new ObjectOwnerPacket.ObjectDataBlock();

    objDeedPacket.ObjectData[0].ObjectLocalID = localID;

    Client.network.SendPacket(objDeedPacket, simulator);
  }

  /// <summary>
  /// Deed multiple objects (prims) to a group, Objects must be shared with group which
  /// can be accomplished with SetPermissions()
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="localIDs">An array which contains the IDs of the objects to deed</param>
  /// <param name="groupOwner">The <seealso cref="UUID"/> of the group to deed the object to</param>
  public void DeedObjects(Simulator simulator, List<Long> localIDs, UUID groupOwner)
  {
    ObjectOwnerPacket packet = new ObjectOwnerPacket();
    packet.AgentData.AgentID = Client.self.getAgentID();
    packet.AgentData.SessionID = Client.self.getSessionID();

    // Can only be use in God mode
    packet.HeaderData.Override = false;
    packet.HeaderData.OwnerID = UUID.Zero;
    packet.HeaderData.GroupID = groupOwner;

    packet.ObjectData = new ObjectOwnerPacket.ObjectDataBlock[localIDs.size()];

    for (int i = 0; i < localIDs.size(); i++)
    {
      packet.ObjectData[i] = new ObjectOwnerPacket.ObjectDataBlock();
      packet.ObjectData[i].ObjectLocalID = localIDs.get(i);
    }
    Client.network.SendPacket(packet, simulator);
  }

  /// <summary>
  /// Set the permissions on multiple objects
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the objects reside</param>
  /// <param name="localIDs">An array which contains the IDs of the objects to set the permissions on</param>
  /// <param name="who">The new Who mask to set</param>
  /// <param name="permissions">Which permission to modify</param>
  /// <param name="set">The new state of permission</param>
  public void SetPermissions(Simulator simulator, List<Long> localIDs, PermissionWho who,
      PermissionMask permissions, boolean set)
  {
    ObjectPermissionsPacket packet = new ObjectPermissionsPacket();

    packet.AgentData.AgentID = Client.self.getAgentID();
    packet.AgentData.SessionID = Client.self.getSessionID();

    // Override can only be used by gods
    packet.HeaderData.Override = false;

    packet.ObjectData = new ObjectPermissionsPacket.ObjectDataBlock[localIDs.size()];

    for (int i = 0; i < localIDs.size(); i++)
    {
      packet.ObjectData[i] = new ObjectPermissionsPacket.ObjectDataBlock();

      packet.ObjectData[i].ObjectLocalID = localIDs.get(i);
      packet.ObjectData[i].Field = (byte)who.getIndex();
      packet.ObjectData[i].Mask = (long)permissions.getIndex();
      packet.ObjectData[i].Set = Utils.booleanToBytes(set);
    }

    Client.network.SendPacket(packet, simulator);
  }

  /// <summary>
  /// Request additional properties for an object
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="objectID"></param>
  public void RequestObjectPropertiesFamily(Simulator simulator, UUID objectID)
  {
    RequestObjectPropertiesFamily(simulator, objectID, true);
  }

  /// <summary>
  /// Request additional properties for an object
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="objectID">Absolute UUID of the object</param>
  /// <param name="reliable">Whether to require server acknowledgement of this request</param>
  public void RequestObjectPropertiesFamily(Simulator simulator, UUID objectID, boolean reliable)
  {
    RequestObjectPropertiesFamilyPacket properties = new RequestObjectPropertiesFamilyPacket();
    properties.AgentData.AgentID = Client.self.getAgentID();
    properties.AgentData.SessionID = Client.self.getSessionID();
    properties.ObjectData.ObjectID = objectID;
    // TODO: RequestFlags is typically only for bug report submissions, but we might be able to
    // use it to pass an arbitrary uint back to the callback
    properties.ObjectData.RequestFlags = 0;

    properties.header.Reliable = reliable;

    Client.network.SendPacket(properties, simulator);
  }

  /// <summary>
  /// Set the ownership of a list of objects to the specified group
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the objects reside</param>
  /// <param name="localIds">An array which contains the IDs of the objects to set the group id on</param>
  /// <param name="groupID">The Groups ID</param>
  public void SetObjectsGroup(Simulator simulator, List<Long> localIds, UUID groupID)
  {
    ObjectGroupPacket packet = new ObjectGroupPacket();
    packet.AgentData.AgentID = Client.self.getAgentID();
    packet.AgentData.GroupID = groupID;
    packet.AgentData.SessionID = Client.self.getSessionID();

    packet.ObjectData = new ObjectGroupPacket.ObjectDataBlock[localIds.size()];
    for (int i = 0; i < localIds.size(); i++)
    {
      packet.ObjectData[i] = new ObjectGroupPacket.ObjectDataBlock();
      packet.ObjectData[i].ObjectLocalID = localIds.get(i);
    }

    Client.network.SendPacket(packet, simulator);
  }

  /// <summary>
  /// Update current URL of the previously set prim media
  /// </summary>
  /// <param name="primID">UUID of the prim</param>
  /// <param name="newURL">Set current URL to this</param>
  /// <param name="face">Prim face number</param>
  /// <param name="sim">Simulator in which prim is located</param>
  public void NavigateObjectMedia(UUID primID, int face, String newURL, Simulator sim) throws Exception
  {
    URI url;
    if (sim.Caps != null && null != (url = sim.Caps.CapabilityURI("ObjectMediaNavigate")))
    {
      ObjectMediaNavigateMessage req = new ObjectMediaNavigateMessage();
      req.PrimID = primID;
      req.URL = newURL;
      req.Face = face;

      CapsHttpClient request = new CapsHttpClient(url);

      request.addRequestCompleteObserver(new EventObserver<CapsHttpRequestCompletedArg>()
      {
        public void handleEvent(Observable arg0, CapsHttpRequestCompletedArg arg1) {
          //      System.out.println("RequestCompletedObserver called ...");
          CapsHttpRequestCompletedArg rcha = (CapsHttpRequestCompletedArg) arg1;
          if (rcha.getError() != null)
          {
            JLogger.error("ObjectMediaNavigate: " + Utils.getExceptionStackTraceAsString(rcha.getError()));
          }
        }
      });

      request.BeginGetResponse(req.Serialize(), OSDFormat.Xml, Client.settings.CAPS_TIMEOUT);
    }
    else
    {
      JLogger.error("ObjectMediaNavigate capability not available");
    }
  }

  /// <summary>
  /// Set object media
  /// </summary>
  /// <param name="primID">UUID of the prim</param>
  /// <param name="faceMedia">Array the length of prims number of faces. Null on face indexes where there is
  /// no media, <seealso cref="MediaEntry"/> on faces which contain the media</param>
  /// <param name="sim">Simulatior in which prim is located</param>
  public void UpdateObjectMedia(UUID primID, MediaEntry[] faceMedia, Simulator sim) throws Exception
  {
    URI url;
    if (sim.Caps != null && null != (url = sim.Caps.CapabilityURI("ObjectMedia")))
    {
      LindenMessages.ObjectMediaUpdate req = new LindenMessages.ObjectMediaUpdate();
      req.PrimID = primID;
      req.FaceMedia = faceMedia;
      req.Verb = "UPDATE";

      CapsHttpClient request = new CapsHttpClient(url);
      request.addRequestCompleteObserver(new EventObserver<CapsHttpRequestCompletedArg>()
      {
        public void handleEvent(Observable arg0, CapsHttpRequestCompletedArg arg1) {
          //      System.out.println("RequestCompletedObserver called ...");
          CapsHttpRequestCompletedArg rcha = (CapsHttpRequestCompletedArg) arg1;
          if (rcha.getError() != null)
          {
            JLogger.error("ObjectMediaUpdate: " + Utils.getExceptionStackTraceAsString(rcha.getError()));
          }
        }
      });

      request.BeginGetResponse(req.Serialize(), OSDFormat.Xml, Client.settings.CAPS_TIMEOUT);
    }
    else
    {
      JLogger.error("ObjectMedia capability not available");
    }
  }

  /// <summary>
  /// Retrieve information about object media
  /// </summary>
  /// <param name="primID">UUID of the primitive</param>
  /// <param name="sim">Simulator where prim is located</param>
  /// <param name="callback">Call this callback when done</param>
  public void RequestObjectMedia(final UUID primID, final Simulator sim, final Action<ObjectMediaCallbackArgs> callback) throws Exception
  {
    URI url;
    if (sim.Caps != null && null != (url = sim.Caps.CapabilityURI("ObjectMedia")))
    {
      LindenMessages.ObjectMediaRequest req = new LindenMessages.ObjectMediaRequest();
      req.PrimID = primID;
      req.Verb = "GET";

      CapsHttpClient request = new CapsHttpClient(url);

      request.addRequestCompleteObserver(new EventObserver<CapsHttpRequestCompletedArg>()
      {
        public void handleEvent(Observable arg0, CapsHttpRequestCompletedArg arg1) {
          //      System.out.println("RequestCompletedObserver called ...");
          CapsHttpRequestCompletedArg rcha = (CapsHttpRequestCompletedArg) arg1;
          if (rcha.getResult() == null)
          {
            JLogger.error("Failed retrieving ObjectMedia data");
            try { callback.execute( new ObjectMediaCallbackArgs(false, "", null)); }
            catch (Exception ex) { JLogger.error(Utils.getExceptionStackTraceAsString(ex)); }
            return;
          }

          LindenMessages.ObjectMediaMessage msg = new LindenMessages.ObjectMediaMessage();
          msg.Deserialize((OSDMap)rcha.getResult());

          if (msg.Request instanceof LindenMessages.ObjectMediaResponse)
          {
            LindenMessages.ObjectMediaResponse response = (LindenMessages.ObjectMediaResponse)msg.Request;

            if (Client.settings.OBJECT_TRACKING)
            {
//              final Primitive[] primarray = new Primitive[]{null};
//              Primitive prim = null;
//              sim.ObjectsPrimitives.foreach(new Action<Entry<Long, Primitive>>()
//                  {
//                public void execute(
//                    Entry<Long, Primitive> t) {
//                  if(t.getValue().ID.equals(primID))
//                  {
//                    primarray[0] = t.getValue();
//                  }
//                }
//                  });
//              prim = primarray[0];
             
              Primitive prim = sim.ObjectsPrimitives.get(primID);

              //                                    Find((Primitive p) => { return p.ID == primID; });
              if (prim != null)
              {
                prim.MediaVersion = response.Version;
                prim.FaceMedia = response.FaceMedia;
              }
            }

            try { callback.execute( new ObjectMediaCallbackArgs(true, response.Version, response.FaceMedia)); }
            catch (Exception ex) { JLogger.error(Utils.getExceptionStackTraceAsString(ex)); }
          }
          else
          {
            try { callback.execute( new ObjectMediaCallbackArgs(false, "", null)); }
            catch (Exception ex) { JLogger.error(Utils.getExceptionStackTraceAsString(ex)); }
          }
        }
      });

      request.BeginGetResponse(req.Serialize(), OSDFormat.Xml, Client.settings.CAPS_TIMEOUT);
    }
    else
    {
      JLogger.error("ObjectMedia capability not available");
      try { callback.execute( new ObjectMediaCallbackArgs(false, "", null)); }
      catch (Exception ex) { JLogger.error(Utils.getExceptionStackTraceAsString(ex)); }
    }
  }
  //endregion


  //region Packet Handlers

  /// <summary>Process an incoming packet and raise the appropriate events</summary>
  /// <param name="sender">The sender</param>
  /// <param name="e">The EventArgs object containing the packet data</param>
  protected void ObjectUpdateHandler(Object sender, PacketReceivedEventArgs e) throws Exception
  {
    JLogger.debug("Starting ObjectUpdateHandler ...");
    Packet packet = e.getPacket();
    final Simulator simulator = e.getSimulator();

    final ObjectUpdatePacket update = (ObjectUpdatePacket)packet;
    UpdateDilation(e.getSimulator(), update.RegionData.TimeDilation);

    for (int b = 0; b < update.ObjectData.length; b++)
    {
      //JLogger.debug("Decoding ObjectData index: " + b);
      ObjectUpdatePacket.ObjectDataBlock block = update.ObjectData[b];

      ObjectMovementUpdate objectupdate = new ObjectMovementUpdate();
      //Vector4 collisionPlane = Vector4.Zero;
      //Vector3 position;
      //Vector3 velocity;
      //Vector3 acceleration;
      //Quaternion rotation;
      //Vector3 angularVelocity;
      NameValue[] nameValues;
      boolean attachment = false;
      PCode pcode = PCode.get(block.PCode);

      //region Relevance check

      // Check if we are interested in this object
      if (!Client.settings.ALWAYS_DECODE_OBJECTS)
      {
        switch (pcode)
        {
        case Grass:
        case Tree:
        case NewTree:
        case Prim:
          JLogger.debug("Got a Grass, Tree, NewTree or Prim....");
          if (onObjectUpdate == null) continue;
          break;
        case Avatar:
          JLogger.debug("Got an Avatar...");
          // Make an exception for updates about our own agent
          if (!block.FullID.equals(Client.self.getAgentID()) && onAvatarUpdate == null) continue;
          break;
        case ParticleSystem:
          JLogger.debug("Got an Partical System ... Going to Next Block");
          continue; // TODO: Do something with these
        }
      }

      //endregion Relevance check

      //region NameValue parsing

      String nameValue = Utils.bytesWithTrailingNullByteToString(block.NameValue);
      if (nameValue.length() > 0)
      {
        String[] lines = nameValue.split("\n");
        nameValues = new NameValue[lines.length];

        for (int i = 0; i < lines.length; i++)
        {
          if (!Utils.isNullOrEmpty(lines[i]))
          {
            NameValue nv = new NameValue(lines[i]);
            if (nv.Name.equals("AttachItemID")) attachment = true;
            nameValues[i] = nv;
          }
        }
      }
      else
      {
        nameValues = new NameValue[0];
      }

      //JLogger.debug("Got NameValue String: " + nameValue + " $Parsed Values$ " + NameValue.NameValuesToString(nameValues));
     
      //endregion NameValue parsing

      //region Decode Object (primitive) parameters
      ConstructionData data = new ConstructionData();
      data.State = block.State;
      data.Material = Material.get(block.Material);
      data.PathCurve = PathCurve.get(block.PathCurve);
      data.profileCurve = block.ProfileCurve;
      data.PathBegin = Primitive.UnpackBeginCut(block.PathBegin);
      data.PathEnd = Primitive.UnpackEndCut(block.PathEnd);
      data.PathScaleX = Primitive.UnpackPathScale(block.PathScaleX);
      data.PathScaleY = Primitive.UnpackPathScale(block.PathScaleY);
      data.PathShearX = Primitive.UnpackPathShear((byte)block.PathShearX);
      data.PathShearY = Primitive.UnpackPathShear((byte)block.PathShearY);
      data.PathTwist = Primitive.UnpackPathTwist(block.PathTwist);
      data.PathTwistBegin = Primitive.UnpackPathTwist(block.PathTwistBegin);
      data.PathRadiusOffset = Primitive.UnpackPathTwist(block.PathRadiusOffset);
      data.PathTaperX = Primitive.UnpackPathTaper(block.PathTaperX);
      data.PathTaperY = Primitive.UnpackPathTaper(block.PathTaperY);
      data.PathRevolutions = Primitive.UnpackPathRevolutions(block.PathRevolutions);
      data.PathSkew = Primitive.UnpackPathTwist(block.PathSkew);
      data.ProfileBegin = Primitive.UnpackBeginCut(block.ProfileBegin);
      data.ProfileEnd = Primitive.UnpackEndCut(block.ProfileEnd);
      data.ProfileHollow = Primitive.UnpackProfileHollow(block.ProfileHollow);
      data.PCode = pcode;
      //endregion

      //region Decode Additional packed parameters in ObjectData
      int pos = 0;
      int i = block.ObjectData.length;
     
      //TODO following comment is only for debugging
      JLogger.debug("ObjectUpdateHandler Got Object Length : " + i);
      boolean search = true;
      while(search)
      {
        search = false;
        switch (i)
        {
        case 76:
          // Collision normal for avatar
          objectupdate.CollisionPlane = new Vector4(block.ObjectData, pos);
          pos += 16;
         
          //Keep on decoding
          i = 60;
          search = true;
          break;
        case 60:
          // Position
          objectupdate.Position = new Vector3(block.ObjectData, pos);
          pos += 12;
          // Velocity
          objectupdate.Velocity = new Vector3(block.ObjectData, pos);
          pos += 12;
          // Acceleration
          objectupdate.Acceleration = new Vector3(block.ObjectData, pos);
          pos += 12;
          // Rotation (theta)
          objectupdate.Rotation = new Quaternion(block.ObjectData, pos, true);
          pos += 12;
          // Angular velocity (omega)
          objectupdate.AngularVelocity = new Vector3(block.ObjectData, pos);
          pos += 12;

          break;
        case 48:
          // Collision normal for avatar
          objectupdate.CollisionPlane = new Vector4(block.ObjectData, pos);
          pos += 16;
         
          //Keep on decoding
          i = 32;
          search = true;
          break;
        case 32:
          // The data is an array of unsigned shorts
          //TODO need to handle Little Endian Data
          // Position
          objectupdate.Position = new Vector3(
              Utils.UInt16ToFloat(block.ObjectData, pos, -0.5f * 256.0f, 1.5f * 256.0f),
              Utils.UInt16ToFloat(block.ObjectData, pos + 2, -0.5f * 256.0f, 1.5f * 256.0f),
              Utils.UInt16ToFloat(block.ObjectData, pos + 4, -256.0f, 3.0f * 256.0f));
          pos += 6;
          // Velocity
          objectupdate.Velocity = new Vector3(
              Utils.UInt16ToFloat(block.ObjectData, pos, -256.0f, 256.0f),
              Utils.UInt16ToFloat(block.ObjectData, pos + 2, -256.0f, 256.0f),
              Utils.UInt16ToFloat(block.ObjectData, pos + 4, -256.0f, 256.0f));
          pos += 6;
          // Acceleration
          objectupdate.Acceleration = new Vector3(
              Utils.UInt16ToFloat(block.ObjectData, pos, -256.0f, 256.0f),
              Utils.UInt16ToFloat(block.ObjectData, pos + 2, -256.0f, 256.0f),
              Utils.UInt16ToFloat(block.ObjectData, pos + 4, -256.0f, 256.0f));
          pos += 6;
          // Rotation (theta)
          objectupdate.Rotation = new Quaternion(
              Utils.UInt16ToFloat(block.ObjectData, pos, -1.0f, 1.0f),
              Utils.UInt16ToFloat(block.ObjectData, pos + 2, -1.0f, 1.0f),
              Utils.UInt16ToFloat(block.ObjectData, pos + 4, -1.0f, 1.0f),
              Utils.UInt16ToFloat(block.ObjectData, pos + 6, -1.0f, 1.0f));
          pos += 8;
          // Angular velocity (omega)
          objectupdate.AngularVelocity = new Vector3(
              Utils.UInt16ToFloat(block.ObjectData, pos, -256.0f, 256.0f),
              Utils.UInt16ToFloat(block.ObjectData, pos + 2, -256.0f, 256.0f),
              Utils.UInt16ToFloat(block.ObjectData, pos + 4, -256.0f, 256.0f));
          pos += 6;

          break;
        case 16:
          // The data is an array of single bytes (8-bit numbers)
          //TODO need to handle Little Endian Data
          // Position
          objectupdate.Position = new Vector3(
              Utils.byteToFloat(block.ObjectData, pos, -256.0f, 256.0f),
              Utils.byteToFloat(block.ObjectData, pos + 1, -256.0f, 256.0f),
              Utils.byteToFloat(block.ObjectData, pos + 2, -256.0f, 256.0f));
          pos += 3;
          // Velocity
          objectupdate.Velocity = new Vector3(
              Utils.byteToFloat(block.ObjectData, pos, -256.0f, 256.0f),
              Utils.byteToFloat(block.ObjectData, pos + 1, -256.0f, 256.0f),
              Utils.byteToFloat(block.ObjectData, pos + 2, -256.0f, 256.0f));
          pos += 3;
          // Accleration
          objectupdate.Acceleration = new Vector3(
              Utils.byteToFloat(block.ObjectData, pos, -256.0f, 256.0f),
              Utils.byteToFloat(block.ObjectData, pos + 1, -256.0f, 256.0f),
              Utils.byteToFloat(block.ObjectData, pos + 2, -256.0f, 256.0f));
          pos += 3;
          // Rotation
          objectupdate.Rotation = new Quaternion(
              Utils.byteToFloat(block.ObjectData, pos, -1.0f, 1.0f),
              Utils.byteToFloat(block.ObjectData, pos + 1, -1.0f, 1.0f),
              Utils.byteToFloat(block.ObjectData, pos + 2, -1.0f, 1.0f),
              Utils.byteToFloat(block.ObjectData, pos + 3, -1.0f, 1.0f));
          pos += 4;
          // Angular Velocity
          objectupdate.AngularVelocity = new Vector3(
              Utils.byteToFloat(block.ObjectData, pos, -256.0f, 256.0f),
              Utils.byteToFloat(block.ObjectData, pos + 1, -256.0f, 256.0f),
              Utils.byteToFloat(block.ObjectData, pos + 2, -256.0f, 256.0f));
          pos += 3;

          break;
        default:
          JLogger.warn("Unhandled: Got an ObjectUpdate block with ObjectUpdate field length of " +
              block.ObjectData.length);
          continue;
        } //switch
      }
      //endregion

      if(objectupdate.Position == null)
        JLogger.warn("ObjectUpdateHandler: Object Position found to be null after update");
     
      // Determine the object type and create the appropriate class
      switch (pcode)
      {
      //region Prim and Foliage
      case Grass:
      case Tree:
      case NewTree:
      case Prim:

        boolean isNewObject;
        //                          lock (simulator.ObjectsPrimitives.Dictionary)
        isNewObject = !simulator.ObjectsPrimitives.containsKey(block.ID);

        final Primitive prim = GetPrimitive(simulator, block.ID, block.FullID);

        // Textures
        objectupdate.Textures = new TextureEntry(block.TextureEntry, 0,
            block.TextureEntry.length);

        onObjectDataBlockUpdate.raiseEvent(new ObjectDataBlockUpdateEventArgs(simulator, prim, data, block, objectupdate, nameValues));

        //region Update Prim Info with decoded data
        prim.Flags = block.UpdateFlags;
        //JLogger.debug("Block UpdateFlags: " + block.UpdateFlags + Utils.bytesToHexDebugString(Utils.int64ToBytes(block.UpdateFlags), ""));
       
        if ((prim.Flags & PrimFlags.ZlibCompressed.getIndex()) != 0)
        {
          JLogger.warn("Got a ZlibCompressed ObjectUpdate, implement me!");
          continue;
        }

        // Automatically request ObjectProperties for prim if it was rezzed selected.
        if (( prim.Flags & PrimFlags.CreateSelected.getIndex()) != 0)
        {
          SelectObject(simulator, prim.LocalID);
        }

        prim.NameValues = nameValues;
        prim.LocalID = block.ID;
        prim.ID = block.FullID;
        prim.ParentID = block.ParentID;
        prim.RegionHandle = update.RegionData.RegionHandle;
        prim.Scale = block.Scale;
        prim.ClickAction = ClickAction.get(block.ClickAction);
        prim.OwnerID = block.OwnerID;
        prim.MediaURL = Utils.bytesWithTrailingNullByteToString(block.MediaURL);
        prim.Text = Utils.bytesWithTrailingNullByteToString(block.Text);
        prim.TextColor = new Color4(block.TextColor, 0, false, true);
        prim.IsAttachment = attachment;

        // Sound information
        prim.Sound = block.Sound;
        prim.SoundFlags = block.Flags;
        prim.SoundGain = block.Gain;
        prim.SoundRadius = block.Radius;

        // Joint information
        prim.Joint = JointType.get(block.JointType);
        prim.JointPivot = block.JointPivot;
        prim.JointAxisOrAnchor = block.JointAxisOrAnchor;

        // Object parameters
        prim.PrimData = data;

        // Textures, texture animations, particle system, and extra params
        prim.Textures = objectupdate.Textures;

        prim.TextureAnim = new TextureAnimation(block.TextureAnim, 0);
       
        //FIXME need to fix the following exception
        try{
          prim.ParticleSys = new ParticleSystem(block.PSBlock, 0);
        }
        catch(Exception ex)
        {
          JLogger.warn("Error while decoding ParticleSystem from packet: "
              + Utils.getExceptionStackTraceAsString(ex));
        }
       
        prim.SetExtraParamsFromBytes(block.ExtraParams, 0);

        // PCode-specific data
        switch (pcode)
        {
        case Grass:
        case Tree:
        case NewTree:
          if (block.Data.length == 1)
            prim.TreeSpecies = Tree.get(block.Data[0]);
          else
            JLogger.warn("Got a foliage update with an invalid TreeSpecies field");
          //    prim.ScratchPad = Utils.EmptyBytes;
          //    break;
          //default:
          //    prim.ScratchPad = new byte[block.Data.Length];
          //    if (block.Data.Length > 0)
          //        Buffer.BlockCopy(block.Data, 0, prim.ScratchPad, 0, prim.ScratchPad.Length);
          break;
        }
        prim.ScratchPad = Utils.EmptyBytes;

        // Packed parameters
        prim.CollisionPlane = objectupdate.CollisionPlane;
        prim.Position = objectupdate.Position;
        prim.Velocity = objectupdate.Velocity;
        prim.Acceleration = objectupdate.Acceleration;
        prim.Rotation = objectupdate.Rotation;
        prim.AngularVelocity = objectupdate.AngularVelocity;
       
        JLogger.debug("Primitive " + prim.LocalID + " Position " + prim.Position.toString());
       
        //endregion

        final boolean isNewObject2 = isNewObject;

        //        EventHandler<PrimEventArgs> handler = m_ObjectUpdate;
        if (onObjectUpdate != null)
        {
          final boolean attachment2 = attachment;
          threadPool.execute(new Runnable(){
            public void run()
            {
              onObjectUpdate.raiseEvent(new PrimEventArgs(simulator, prim, update.RegionData.TimeDilation, isNewObject2, attachment2));
            }
          });

          //          ThreadPool.QueueUserWorkItem(delegate(object o)
          //              { handler(this, new PrimEventArgs(simulator, prim, update.RegionData.TimeDilation, isNewObject, attachment)); });
        }

        break;
        //endregion Prim and Foliage
        //region Avatar
      case Avatar:

        boolean isNewAvatar;
        //        lock (simulator.ObjectsAvatars.Dictionary)
        isNewAvatar = !simulator.ObjectsAvatars.containsKey(block.ID);

        if(block.FullID.equals(UUID.Zero))
          JLogger.warn(String.format("Received Avatar with Zero ID PCcode %d LocalID %d", block.PCode, block.ID));
       
        // Update some internals if this is our avatar
        if (block.FullID.equals(Client.self.getAgentID()) && simulator.equals(Client.network.getCurrentSim()))
        {
          //region Update Client.Self

          // We need the local ID to recognize terse updates for our agent
          Client.self.setLocalID(block.ID);

          // Packed parameters
          Client.self.setCollisionPlane(objectupdate.CollisionPlane);
          Client.self.setRelativePosition(objectupdate.Position);
          Client.self.setVelocity(objectupdate.Velocity);
          Client.self.setAcceleration(objectupdate.Acceleration);
          Client.self.setRelativeRotation(objectupdate.Rotation);
          Client.self.setAngularVelocity(objectupdate.AngularVelocity);

          //endregion
        }

        //region Create an Avatar from the decoded data

        Avatar avatar = GetAvatar(simulator, block.ID, block.FullID);

        objectupdate.Avatar = true;
        // Textures
        objectupdate.Textures = new TextureEntry(block.TextureEntry, 0,
            block.TextureEntry.length);

        onObjectDataBlockUpdate.raiseEvent(new ObjectDataBlockUpdateEventArgs(simulator, avatar, data, block, objectupdate, nameValues));

        long oldSeatID = avatar.ParentID;

        avatar.ID = block.FullID;
        avatar.LocalID = block.ID;
        avatar.Scale = block.Scale;
        avatar.CollisionPlane = objectupdate.CollisionPlane;
        avatar.Position = objectupdate.Position;
        avatar.Velocity = objectupdate.Velocity;
        avatar.Acceleration = objectupdate.Acceleration;
        avatar.Rotation = objectupdate.Rotation;
        avatar.AngularVelocity = objectupdate.AngularVelocity;
        avatar.NameValues = nameValues;
        avatar.PrimData = data;
        if (block.Data.length > 0)
        {
          JLogger.warn("Unexpected Data field for an avatar update, length " + block.Data.length);
        }
        avatar.ParentID = block.ParentID;
        avatar.RegionHandle = update.RegionData.RegionHandle;
        JLogger.debug("Set an avatar: " + avatar.getName()
            + "\n with name values:\n" + NameValue.NameValuesToString(avatar.NameValues));
       
        SetAvatarSittingOn(simulator, avatar, block.ParentID, oldSeatID);

        // Textures
        avatar.Textures = objectupdate.Textures;

        //endregion Create an Avatar from the decoded data

        onAvatarUpdate.raiseEvent(new AvatarUpdateEventArgs(simulator, avatar, update.RegionData.TimeDilation, isNewAvatar));

        break;
        //endregion Avatar
      case ParticleSystem:
        DecodeParticleUpdate(block);
        // TODO: Create a callback for particle updates
        break;
      default:
        JLogger.debug("Got an ObjectUpdate block with an unrecognized PCode " + pcode.toString());
        break;
      }
    }
  }

  protected void DecodeParticleUpdate(ObjectUpdatePacket.ObjectDataBlock block)
  {
    // TODO: Handle ParticleSystem ObjectUpdate blocks

    // float bounce_b
    // Vector4 scale_range
    // Vector4 alpha_range
    // Vector3 vel_offset
    // float dist_begin_fadeout
    // float dist_end_fadeout
    // UUID image_uuid
    // long flags
    // byte createme
    // Vector3 diff_eq_alpha
    // Vector3 diff_eq_scale
    // byte max_particles
    // byte initial_particles
    // float kill_plane_z
    // Vector3 kill_plane_normal
    // float bounce_plane_z
    // Vector3 bounce_plane_normal
    // float spawn_range
    // float spawn_frequency
    // float spawn_frequency_range
    // Vector3 spawn_direction
    // float spawn_direction_range
    // float spawn_velocity
    // float spawn_velocity_range
    // float speed_limit
    // float wind_weight
    // Vector3 current_gravity
    // float gravity_weight
    // float global_lifetime
    // float individual_lifetime
    // float individual_lifetime_range
    // float alpha_decay
    // float scale_decay
    // float distance_death
    // float damp_motion_factor
    // Vector3 wind_diffusion_factor
  }

  /// <summary>
  /// A terse object update, used when a transformation matrix or
  /// velocity/acceleration for an object changes but nothing else
  /// (scale/position/rotation/acceleration/velocity)
  /// </summary>
  /// <param name="sender">The sender</param>
  /// <param name="e">The EventArgs object containing the packet data</param>
  protected void ImprovedTerseObjectUpdateHandler(Object sender, PacketReceivedEventArgs e)
  {
    Packet packet = e.getPacket();
    final Simulator simulator = e.getSimulator();

    final ImprovedTerseObjectUpdatePacket terse = (ImprovedTerseObjectUpdatePacket)packet;
    UpdateDilation(simulator, terse.RegionData.TimeDilation);

    for (int i = 0; i < terse.ObjectData.length; i++)
    {
      ImprovedTerseObjectUpdatePacket.ObjectDataBlock block = terse.ObjectData[i];

      try
      {
        int pos = 4;
        long localid = Utils.bytesToUIntLit(block.Data, 0);

        // Check if we are interested in this update
        if (!Client.settings.ALWAYS_DECODE_OBJECTS
            && localid != Client.self.getLocalID()
            && onTerseObjectUpdate == null)
        {
          continue;
        }

        //region Decode update data

        final ObjectMovementUpdate update = new ObjectMovementUpdate();

        // LocalID
        update.LocalID = localid;
        // State
        update.State = block.Data[pos++];
        // Avatar boolean
        update.Avatar = (block.Data[pos++] != 0);
        // Collision normal for avatar
        if (update.Avatar)
        {
          update.CollisionPlane = new Vector4(block.Data, pos);
          pos += 16;
        }
        // Position
        update.Position = new Vector3(block.Data, pos);
        pos += 12;
        // Velocity
        update.Velocity = new Vector3(
            Utils.UInt16ToFloat(block.Data, pos, -128.0f, 128.0f),
            Utils.UInt16ToFloat(block.Data, pos + 2, -128.0f, 128.0f),
            Utils.UInt16ToFloat(block.Data, pos + 4, -128.0f, 128.0f));
        pos += 6;
        // Acceleration
        update.Acceleration = new Vector3(
            Utils.UInt16ToFloat(block.Data, pos, -64.0f, 64.0f),
            Utils.UInt16ToFloat(block.Data, pos + 2, -64.0f, 64.0f),
            Utils.UInt16ToFloat(block.Data, pos + 4, -64.0f, 64.0f));
        pos += 6;
        // Rotation (theta)
        update.Rotation = new Quaternion(
            Utils.UInt16ToFloat(block.Data, pos, -1.0f, 1.0f),
            Utils.UInt16ToFloat(block.Data, pos + 2, -1.0f, 1.0f),
            Utils.UInt16ToFloat(block.Data, pos + 4, -1.0f, 1.0f),
            Utils.UInt16ToFloat(block.Data, pos + 6, -1.0f, 1.0f));
        pos += 8;
        // Angular velocity (omega)
        update.AngularVelocity = new Vector3(
            Utils.UInt16ToFloat(block.Data, pos, -64.0f, 64.0f),
            Utils.UInt16ToFloat(block.Data, pos + 2, -64.0f, 64.0f),
            Utils.UInt16ToFloat(block.Data, pos + 4, -64.0f, 64.0f));
        pos += 6;

        // Textures
        // FIXME: Why are we ignoring the first four bytes here?
        if (block.TextureEntry.length != 0)
          update.Textures = new TextureEntry(block.TextureEntry, 4, block.TextureEntry.length - 4);

        //endregion Decode update data
       
        final Primitive obj = !Client.settings.OBJECT_TRACKING ? null : (update.Avatar) ?
            (Primitive)GetAvatar(simulator, update.LocalID, UUID.Zero) :
              (Primitive)GetPrimitive(simulator, update.LocalID, UUID.Zero);

            // Fire the pre-emptive notice (before we stomp the object)
            //                        EventHandler<TerseObjectUpdateEventArgs> handler = m_TerseObjectUpdate;
            //                        if (handler != null)
            //                        {
            //                            ThreadPool.QueueUserWorkItem(delegate(object o)
            //                            { handler(this, new TerseObjectUpdateEventArgs(simulator, obj, update, terse.RegionData.TimeDilation)); });
            //                        }

            threadPool.execute(new Runnable(){
              public void run()
              {
                onTerseObjectUpdate.raiseEvent(new TerseObjectUpdateEventArgs(simulator, obj, update, terse.RegionData.TimeDilation));
              }
            });

            //region Update Client.Self
            if (update.LocalID == Client.self.getLocalID())
            {
              Client.self.setCollisionPlane(update.CollisionPlane);
              Client.self.setRelativePosition(update.Position);
              Client.self.setVelocity(update.Velocity);
              Client.self.setAcceleration(update.Acceleration);
              Client.self.setRelativeRotation(update.Rotation);
              Client.self.setAngularVelocity(update.AngularVelocity);
            }
            //endregion Update Client.Self
            if (Client.settings.OBJECT_TRACKING && obj != null)
            {
              obj.Position = update.Position;
              obj.Rotation = update.Rotation;
              obj.Velocity = update.Velocity;
              obj.CollisionPlane = update.CollisionPlane;
              obj.Acceleration = update.Acceleration;
              obj.AngularVelocity = update.AngularVelocity;
              obj.PrimData.State = update.State;
              if (update.Textures != null)
                obj.Textures = update.Textures;
            }

      }
      catch (Exception ex)
      {
        JLogger.warn(Utils.getExceptionStackTraceAsString(ex));
      }
    }
  }

  /// <summary>Process an incoming packet and raise the appropriate events</summary>
  /// <param name="sender">The sender</param>
  /// <param name="e">The EventArgs object containing the packet data</param>
  protected void ObjectUpdateCompressedHandler(Object sender, PacketReceivedEventArgs e) throws Exception
  {
    Packet packet = e.getPacket();
    Simulator simulator = e.getSimulator();

    ObjectUpdateCompressedPacket update = (ObjectUpdateCompressedPacket)packet;

    for (int b = 0; b < update.ObjectData.length; b++)
    {
      ObjectUpdateCompressedPacket.ObjectDataBlock block = update.ObjectData[b];
      int i = 0;

      try
      {
        // UUID
        UUID FullID = new UUID(block.Data, 0);
        i += 16;
        // Local ID
        //uint
        long LocalID = Utils.bytesToUIntLit(block.Data, i); i += 4;
        //                            (uint)(block.Data[i++] + (block.Data[i++] << 8) +
        //                            (block.Data[i++] << 16) + (block.Data[i++] << 24));

        // PCode
        PCode pcode = PCode.get(block.Data[i++]);

        //region Relevance check

        if (!Client.settings.ALWAYS_DECODE_OBJECTS)
        {
          switch (pcode)
          {
          case Grass:
          case Tree:
          case NewTree:
          case Prim:
            if (onObjectUpdate == null) continue;
            break;
          }
        }

        //endregion Relevance check

        boolean isNew;
        //                        lock (simulator.ObjectsPrimitives.Dictionary)
        isNew = simulator.ObjectsPrimitives.containsKey(LocalID);

        Primitive prim = GetPrimitive(simulator, LocalID, FullID);

        prim.LocalID = LocalID;
        prim.ID = FullID;
        prim.Flags = block.UpdateFlags;
        prim.PrimData.PCode = pcode;

        //region Decode block and update Prim

        // State
        prim.PrimData.State = block.Data[i++];
        // CRC
        i += 4;
        // Material
        prim.PrimData.Material = Material.get(block.Data[i++]);
        // Click action
        prim.ClickAction = ClickAction.get(block.Data[i++]);
        // Scale
        prim.Scale = new Vector3(block.Data, i);
        i += 12;
        // Position
        prim.Position = new Vector3(block.Data, i);
        i += 12;
        // Rotation
        prim.Rotation = new Quaternion(block.Data, i, true);
        i += 12;
        // Compressed flags
        //EnumSet<CompressedFlags>
        long flags = Utils.bytesToUIntLit(block.Data, i);
        i += 4;

        prim.OwnerID = new UUID(block.Data, i);
        i += 16;

        // Angular velocity
        if ((flags & CompressedFlags.HasAngularVelocity.getIndex()) != 0)
        {
          prim.AngularVelocity = new Vector3(block.Data, i);
          i += 12;
        }

        // Parent ID
        if ((flags & CompressedFlags.HasParent.getIndex()) != 0)
        {
          //                            prim.ParentID = (uint)(block.Data[i++] + (block.Data[i++] << 8) +
          //                            (block.Data[i++] << 16) + (block.Data[i++] << 24));
          prim.ParentID = Utils.bytesToUIntLit(block.Data, i); i += 4;
        }
        else
        {
          prim.ParentID = 0;
        }

        // Tree data
        if ((flags & CompressedFlags.Tree.getIndex()) != 0)
        {
          prim.TreeSpecies = Tree.get(block.Data[i++]);
          //prim.ScratchPad = Utils.EmptyBytes;
        }
        // Scratch pad
        else if ((flags & CompressedFlags.ScratchPad.getIndex()) != 0)
        {
          prim.TreeSpecies = Tree.get((byte)0);

          int size = block.Data[i++];
          //prim.ScratchPad = new byte[size];
          //Buffer.BlockCopy(block.Data, i, prim.ScratchPad, 0, size);
          i += size;
        }
        prim.ScratchPad = Utils.EmptyBytes;

        // Floating text
        if ((flags & CompressedFlags.HasText.getIndex())  != 0)
        {
          String text = "";
          while (block.Data[i] != 0)
          {
            text += (char)block.Data[i];
            i++;
          }
          i++;

          // Floating text
          prim.Text = text;

          // Text color
          prim.TextColor = new Color4(block.Data, i, false);
          i += 4;
        }
        else
        {
          prim.Text = "";
        }

        // Media URL
        if ((flags & CompressedFlags.MediaURL.getIndex())  != 0)
        {
          String text = "";
          while (block.Data[i] != 0)
          {
            text += (char)block.Data[i];
            i++;
          }
          i++;

          prim.MediaURL = text;
        }

        // Particle system
        if ((flags & CompressedFlags.HasParticles.getIndex())  != 0)
        {
          prim.ParticleSys = new ParticleSystem(block.Data, i);
          i += 86;
        }

        // Extra parameters
        i += prim.SetExtraParamsFromBytes(block.Data, i);

        //Sound data
        if ((flags & CompressedFlags.HasSound.getIndex())  != 0)
        {
          prim.Sound = new UUID(block.Data, i);
          i += 16;

          prim.SoundGain = Utils.bytesToFloatLit(block.Data, i);
          i += 4;
          prim.SoundFlags = block.Data[i++];
          prim.SoundRadius = Utils.bytesToFloatLit(block.Data, i);
          i += 4;
        }

        // Name values
        if ((flags & CompressedFlags.HasNameValues.getIndex())  != 0)
        {
          String text = "";
          while (block.Data[i] != 0)
          {
            text += (char)block.Data[i];
            i++;
          }
          i++;

          // Parse the name values
          if (text.length() > 0)
          {
            String[] lines = text.split("\n");
            prim.NameValues = new NameValue[lines.length];

            for (int j = 0; j < lines.length; j++)
            {
              if (!Utils.isNullOrEmpty(lines[j]))
              {
                NameValue nv = new NameValue(lines[j]);
                prim.NameValues[j] = nv;
              }
            }
          }
        }

        prim.PrimData.PathCurve = PathCurve.get(block.Data[i++]);
        int pathBegin = Utils.bytesToUInt16Lit(block.Data, i); i += 2;
        prim.PrimData.PathBegin = Primitive.UnpackBeginCut(pathBegin);
        int pathEnd = Utils.bytesToUInt16Lit(block.Data, i); i += 2;
        prim.PrimData.PathEnd = Primitive.UnpackEndCut(pathEnd);
        prim.PrimData.PathScaleX = Primitive.UnpackPathScale(block.Data[i++]);
        prim.PrimData.PathScaleY = Primitive.UnpackPathScale(block.Data[i++]);
        prim.PrimData.PathShearX = Primitive.UnpackPathShear((byte)block.Data[i++]);
        prim.PrimData.PathShearY = Primitive.UnpackPathShear((byte)block.Data[i++]);
        prim.PrimData.PathTwist = Primitive.UnpackPathTwist((byte)block.Data[i++]);
        prim.PrimData.PathTwistBegin = Primitive.UnpackPathTwist((byte)block.Data[i++]);
        prim.PrimData.PathRadiusOffset = Primitive.UnpackPathTwist((byte)block.Data[i++]);
        prim.PrimData.PathTaperX = Primitive.UnpackPathTaper((byte)block.Data[i++]);
        prim.PrimData.PathTaperY = Primitive.UnpackPathTaper((byte)block.Data[i++]);
        prim.PrimData.PathRevolutions = Primitive.UnpackPathRevolutions(block.Data[i++]);
        prim.PrimData.PathSkew = Primitive.UnpackPathTwist((byte)block.Data[i++]);

        prim.PrimData.profileCurve = block.Data[i++];
        int profileBegin = Utils.bytesToUInt16Lit(block.Data, i); i += 2;
        prim.PrimData.ProfileBegin = Primitive.UnpackBeginCut(profileBegin);
        int profileEnd = Utils.bytesToUInt16Lit(block.Data, i); i += 2;
        prim.PrimData.ProfileEnd = Primitive.UnpackEndCut(profileEnd);
        int profileHollow = Utils.bytesToUInt16Lit(block.Data, i); i += 2;
        prim.PrimData.ProfileHollow = Primitive.UnpackProfileHollow(profileHollow);

        // TextureEntry
        int textureEntryLength = (int)Utils.bytesToUInt(block.Data, i);
        i += 4;
        prim.Textures = new TextureEntry(block.Data, i, textureEntryLength);
        i += textureEntryLength;

        // Texture animation
        if ((flags & CompressedFlags.TextureAnimation.getIndex())  != 0)
        {
          //int textureAnimLength = (int)Utils.BytesToUIntBig(block.Data, i);
          i += 4;
          prim.TextureAnim = new TextureAnimation(block.Data, i);
        }

        //endregion

        prim.IsAttachment = (flags & CompressedFlags.HasNameValues.getIndex())  != 0 && prim.ParentID != 0;

        //region Raise Events

        //                        EventHandler<PrimEventArgs> handler = m_ObjectUpdate;
        if (onObjectUpdate != null)
          onObjectUpdate.raiseEvent( new PrimEventArgs(simulator, prim, update.RegionData.TimeDilation, isNew, prim.IsAttachment));

        //endregion
      }
      catch (IndexOutOfBoundsException ex)
      {
        JLogger.warn("Error decoding an ObjectUpdateCompressed packet\n" + ex.getMessage());
        JLogger.warn(block.toString());
      }
    }
  }

  /// <summary>Process an incoming packet and raise the appropriate events</summary>
  /// <param name="sender">The sender</param>
  /// <param name="e">The EventArgs object containing the packet data</param>
  protected void ObjectUpdateCachedHandler(Object sender, PacketReceivedEventArgs e)
  {
    if (Client.settings.ALWAYS_REQUEST_OBJECTS)
    {
      Packet packet = e.getPacket();
      Simulator simulator = e.getSimulator();

      ObjectUpdateCachedPacket update = (ObjectUpdateCachedPacket)packet;
      List<Long> ids = new ArrayList<Long>(update.ObjectData.length);

      // No object caching implemented yet, so request updates for all of these objects
      for (int i = 0; i < update.ObjectData.length; i++)
      {
        ids.add(update.ObjectData[i].ID);
      }

      RequestObjects(simulator, ids);
    }
  }

  /// <summary>Process an incoming packet and raise the appropriate events</summary>
  /// <param name="sender">The sender</param>
  /// <param name="e">The EventArgs object containing the packet data</param>
  protected void KillObjectHandler(Object sender, PacketReceivedEventArgs e)
  {
    Packet packet = e.getPacket();
    Simulator simulator = e.getSimulator();

    KillObjectPacket kill = (KillObjectPacket)packet;

    // Notify first, so that handler has a chance to get a
    // reference from the ObjectTracker to the object being killed
    for (int i = 0; i < kill.ObjectData.length; i++)
    {
      onKillObject.raiseEvent(new KillObjectEventArgs(simulator, kill.ObjectData[i].ID));
    }


    synchronized (simulator.ObjectsPrimitives.getDictionary())
    {
      List<Long> removeAvatars = new ArrayList<Long>();
      List<Long> removePrims = new ArrayList<Long>();

      if (Client.settings.OBJECT_TRACKING)
      {
        long localID;
        for (int i = 0; i < kill.ObjectData.length; i++)
        {
          localID = kill.ObjectData[i].ID;

          if (simulator.ObjectsPrimitives.getDictionary().containsKey(localID))
            removePrims.add(localID);

          for (Entry<Long, Primitive> prim : simulator.ObjectsPrimitives.getDictionary().entrySet())
          {
            if (prim.getValue().ParentID == localID)
            {
              onKillObject.raiseEvent(new KillObjectEventArgs(simulator, prim.getKey()));
              removePrims.add(prim.getKey());
            }
          }
        }
      }

      if (Client.settings.AVATAR_TRACKING)
      {
        synchronized (simulator.ObjectsAvatars.getDictionary())
        {
          long localID;
          for (int i = 0; i < kill.ObjectData.length; i++)
          {
            localID = kill.ObjectData[i].ID;

            if (simulator.ObjectsAvatars.getDictionary().containsKey(localID))
              removeAvatars.add(localID);

            List<Long> rootPrims = new ArrayList<Long>();

            for (Entry<Long, Primitive> prim : simulator.ObjectsPrimitives.getDictionary().entrySet())
            {
              if (prim.getValue().ParentID == localID)
              {
                onKillObject.raiseEvent(new KillObjectEventArgs(simulator, prim.getKey()));
                removePrims.add(prim.getKey());
                rootPrims.add(prim.getKey());
              }
            }

            for (Entry<Long, Primitive> prim : simulator.ObjectsPrimitives.getDictionary().entrySet())
            {
              if (rootPrims.contains(prim.getValue().ParentID))
              {
                onKillObject.raiseEvent(new KillObjectEventArgs(simulator, prim.getKey()));
                removePrims.add(prim.getKey());
              }
            }
          }

          //Do the actual removing outside of the loops but still inside the lock.
          //This safely prevents the collection from being modified during a loop.
          for (long removeID : removeAvatars)
            simulator.ObjectsAvatars.getDictionary().remove(removeID);
        }
      }

      for (long removeID : removePrims)
        simulator.ObjectsPrimitives.getDictionary().remove(removeID);
    }
  }

  /// <summary>Process an incoming packet and raise the appropriate events</summary>
  /// <param name="sender">The sender</param>
  /// <param name="e">The EventArgs object containing the packet data</param>
  protected void ObjectPropertiesHandler(Object sender, PacketReceivedEventArgs e) throws UnsupportedEncodingException
  {
    Packet packet = e.getPacket();
    Simulator simulator = e.getSimulator();

    ObjectPropertiesPacket op = (ObjectPropertiesPacket)packet;
    ObjectPropertiesPacket.ObjectDataBlock[] datablocks = op.ObjectData;

    for (int i = 0; i < datablocks.length; ++i)
    {
      ObjectPropertiesPacket.ObjectDataBlock objectData = datablocks[i];
      final ObjectProperties props = new ObjectProperties();

      props.ObjectID = objectData.ObjectID;
      props.AggregatePerms = objectData.AggregatePerms;
      props.AggregatePermTextures = objectData.AggregatePermTextures;
      props.AggregatePermTexturesOwner = objectData.AggregatePermTexturesOwner;
      props.Permissions = new Permissions(objectData.BaseMask, objectData.EveryoneMask, objectData.GroupMask,
          objectData.NextOwnerMask, objectData.OwnerMask);
      props.Category = ObjectCategory.get((int)objectData.Category);
      props.CreationDate = Utils.unixTimeToDate(objectData.CreationDate.longValue());
      props.CreatorID = objectData.CreatorID;
      props.Description = Utils.bytesWithTrailingNullByteToString(objectData.Description);
      props.FolderID = objectData.FolderID;
      props.FromTaskID = objectData.FromTaskID;
      props.GroupID = objectData.GroupID;
      props.InventorySerial = objectData.InventorySerial;
      props.ItemID = objectData.ItemID;
      props.LastOwnerID = objectData.LastOwnerID;
      props.Name = Utils.bytesWithTrailingNullByteToString(objectData.Name);
      props.OwnerID = objectData.OwnerID;
      props.OwnershipCost = objectData.OwnershipCost;
      props.SalePrice = objectData.SalePrice;
      props.SaleType = SaleType.get(objectData.SaleType);
      props.SitName = Utils.bytesWithTrailingNullByteToString(objectData.SitName);
      props.TouchName = Utils.bytesWithTrailingNullByteToString(objectData.TouchName);

      int numTextures = objectData.TextureID.length / 16;
      props.TextureIDs = new UUID[numTextures];
      for (int j = 0; j < numTextures; ++j)
        props.TextureIDs[j] = new UUID(objectData.TextureID, j * 16);

      if (Client.settings.OBJECT_TRACKING)
      {
        //                        Primitive findPrim = simulator.ObjectsPrimitives.Find(
        //                            delegate(Primitive prim) { return prim.ID == props.ObjectID; });

//        final Primitive[] primarray = new Primitive[]{null};
//        Primitive findPrim = null;
//        simulator.ObjectsPrimitives.foreach(new Action<Entry<Long, Primitive>>()
//            {
//          public void execute(
//              Entry<Long, Primitive> t) {
//            if(t.getValue().ID.equals(props.ObjectID))
//            {
//              primarray[0] = t.getValue();
//            }
//          }
//            });
//        findPrim = primarray[0];

        Primitive findPrim = simulator.ObjectsPrimitives.get(props.ObjectID);
       
        if (findPrim != null)
        {
          onObjectPropertiesUpdated.raiseEvent(new ObjectPropertiesUpdatedEventArgs(simulator, findPrim, props));

          synchronized (simulator.ObjectsPrimitives.getDictionary())
          {
            if (simulator.ObjectsPrimitives.getDictionary().containsKey(findPrim.LocalID))
              simulator.ObjectsPrimitives.getDictionary().get(findPrim.LocalID).Properties = props;
          }
        }
      }

      onObjectProperties.raiseEvent(new ObjectPropertiesEventArgs(simulator, props));
    }
  }

  /// <summary>Process an incoming packet and raise the appropriate events</summary>
  /// <param name="sender">The sender</param>
  /// <param name="e">The EventArgs object containing the packet data</param>
  protected void ObjectPropertiesFamilyHandler(Object sender, PacketReceivedEventArgs e) throws UnsupportedEncodingException
  {
    Packet packet = e.getPacket();
    Simulator simulator = e.getSimulator();

    final ObjectPropertiesFamilyPacket op = (ObjectPropertiesFamilyPacket)packet;
    ObjectProperties props = new ObjectProperties();

    ReportType requestType = ReportType.get(op.ObjectData.RequestFlags);

    props.ObjectID = op.ObjectData.ObjectID;
//    ObjectCategory a;
    props.Category = ObjectCategory.get((int)op.ObjectData.Category);
    props.Description = Utils.bytesWithTrailingNullByteToString(op.ObjectData.Description);
    props.GroupID = op.ObjectData.GroupID;
    props.LastOwnerID = op.ObjectData.LastOwnerID;
    props.Name = Utils.bytesWithTrailingNullByteToString(op.ObjectData.Name);
    props.OwnerID = op.ObjectData.OwnerID;
    props.OwnershipCost = op.ObjectData.OwnershipCost;
    props.SalePrice = op.ObjectData.SalePrice;
    props.SaleType = SaleType.get(op.ObjectData.SaleType);
    props.Permissions.BaseMask = PermissionMask.get(op.ObjectData.BaseMask);
    props.Permissions.EveryoneMask = PermissionMask.get(op.ObjectData.EveryoneMask);
    props.Permissions.GroupMask = PermissionMask.get(op.ObjectData.GroupMask);
    props.Permissions.NextOwnerMask = PermissionMask.get(op.ObjectData.NextOwnerMask);
    props.Permissions.OwnerMask = PermissionMask.get(op.ObjectData.OwnerMask);

    if (Client.settings.OBJECT_TRACKING)
    {
      //                    Primitive findPrim = simulator.ObjectsPrimitives.Find(
      //                            delegate(Primitive prim) { return prim.ID == op.ObjectData.ObjectID; });

//      final Primitive[] primarray = new Primitive[]{null};
//      Primitive findPrim = null;
//      simulator.ObjectsPrimitives.foreach(new Action<Entry<Long, Primitive>>()
//          {
//        public void execute(
//            Entry<Long, Primitive> t) {
//          if(t.getValue().ID.equals(op.ObjectData.ObjectID))
//          {
//            primarray[0] = t.getValue();
//          }
//        }
//          });
//      findPrim = primarray[0];

      Primitive findPrim = simulator.ObjectsPrimitives.get(op.ObjectData.ObjectID);
     
      if (findPrim != null)
      {
        synchronized (simulator.ObjectsPrimitives.getDictionary())
        {
          if (simulator.ObjectsPrimitives.getDictionary().containsKey(findPrim.LocalID))
          {
            if (simulator.ObjectsPrimitives.getDictionary().get(findPrim.LocalID).Properties == null)
              simulator.ObjectsPrimitives.getDictionary().get(findPrim.LocalID).Properties = new ObjectProperties();
            simulator.ObjectsPrimitives.getDictionary().get(findPrim.LocalID).Properties.SetFamilyProperties(props);
          }
        }
      }
    }

    onObjectPropertiesFamily.raiseEvent(new ObjectPropertiesFamilyEventArgs(simulator, props, requestType));
  }

  /// <summary>Process an incoming packet and raise the appropriate events</summary>
  /// <param name="sender">The sender</param>
  /// <param name="e">The EventArgs object containing the packet data</param>
  protected void PayPriceReplyHandler(Object sender, PacketReceivedEventArgs e)
  {
    if (onPayPriceReply != null)
    {
      Packet packet = e.getPacket();
      Simulator simulator = e.getSimulator();

      PayPriceReplyPacket p = (PayPriceReplyPacket)packet;
      UUID objectID = p.ObjectData.ObjectID;
      int defaultPrice = p.ObjectData.DefaultPayPrice;
      int[] buttonPrices = new int[p.ButtonData.length];

      for (int i = 0; i < p.ButtonData.length; i++)
      {
        buttonPrices[i] = p.ButtonData[i].PayButton;
      }

      onPayPriceReply.raiseEvent(new PayPriceReplyEventArgs(simulator, objectID, defaultPrice, buttonPrices));
    }
  }

  /// <summary>
  ///
  /// </summary>
  /// <param name="capsKey"></param>
  /// <param name="message"></param>
  /// <param name="simulator"></param>
  protected void ObjectPhysicsPropertiesHandler(String capsKey, IMessage message, Simulator simulator)
  {
    ObjectPhysicsPropertiesMessage msg = (ObjectPhysicsPropertiesMessage)message;

    if (Client.settings.OBJECT_TRACKING)
    {
      for (int i = 0; i < msg.ObjectPhysicsProperties.length; i++)
      {
        synchronized (simulator.ObjectsPrimitives.getDictionary())
        {
          if (simulator.ObjectsPrimitives.getDictionary().containsKey(msg.ObjectPhysicsProperties[i].LocalID))
          {
            simulator.ObjectsPrimitives.getDictionary().get(msg.ObjectPhysicsProperties[i].LocalID).PhysicsProps = msg.ObjectPhysicsProperties[i];
          }
        }
      }
    }

    if (onPhysicsProperties != null)
    {
      for (int i = 0; i < msg.ObjectPhysicsProperties.length; i++)
      {
        onPhysicsProperties.raiseEvent(
            new PhysicsPropertiesEventArgs(simulator, msg.ObjectPhysicsProperties[i]));
      }
    }
  }

  //endregion Packet Handlers

  //region Utility Functions

  /// <summary>
  /// Setup construction data for a basic primitive shape
  /// </summary>
  /// <param name="type">Primitive shape to construct</param>
  /// <returns>Construction data that can be plugged into a <seealso cref="Primitive"/></returns>
  public static ConstructionData BuildBasicShape(PrimType type) throws NotSupportedException
  {
    ConstructionData prim = new ConstructionData();
    prim.PCode = PCode.Prim;
    prim.Material = Material.Wood;

    switch (type)
    {
    case Box:
      prim.profileCurve = ProfileCurve.Square.getIndex();
      prim.PathCurve = PathCurve.Line;
      prim.ProfileEnd = 1f;
      prim.PathEnd = 1f;
      prim.PathScaleX = 1f;
      prim.PathScaleY = 1f;
      prim.PathRevolutions = 1f;
      break;
    case Cylinder:
      prim.profileCurve = ProfileCurve.Circle.getIndex();
      prim.PathCurve = PathCurve.Line;
      prim.ProfileEnd = 1f;
      prim.PathEnd = 1f;
      prim.PathScaleX = 1f;
      prim.PathScaleY = 1f;
      prim.PathRevolutions = 1f;
      break;
    case Prism:
      prim.profileCurve = ProfileCurve.Square.getIndex();
      prim.PathCurve = PathCurve.Line;
      prim.ProfileEnd = 1f;
      prim.PathEnd = 1f;
      prim.PathScaleX = 0f;
      prim.PathScaleY = 0f;
      prim.PathRevolutions = 1f;
      break;
    case Ring:
      prim.profileCurve = ProfileCurve.EqualTriangle.getIndex();
      prim.PathCurve = PathCurve.Circle;
      prim.ProfileEnd = 1f;
      prim.PathEnd = 1f;
      prim.PathScaleX = 1f;
      prim.PathScaleY = 0.25f;
      prim.PathRevolutions = 1f;
      break;
    case Sphere:
      prim.profileCurve = ProfileCurve.HalfCircle.getIndex();
      prim.PathCurve = PathCurve.Circle;
      prim.ProfileEnd = 1f;
      prim.PathEnd = 1f;
      prim.PathScaleX = 1f;
      prim.PathScaleY = 1f;
      prim.PathRevolutions = 1f;
      break;
    case Torus:
      prim.profileCurve = ProfileCurve.Circle.getIndex();
      prim.PathCurve = PathCurve.Circle;
      prim.ProfileEnd = 1f;
      prim.PathEnd = 1f;
      prim.PathScaleX = 1f;
      prim.PathScaleY = 0.25f;
      prim.PathRevolutions = 1f;
      break;
    case Tube:
      prim.profileCurve = ProfileCurve.Square.getIndex();
      prim.PathCurve = PathCurve.Circle;
      prim.ProfileEnd = 1f;
      prim.PathEnd = 1f;
      prim.PathScaleX = 1f;
      prim.PathScaleY = 0.25f;
      prim.PathRevolutions = 1f;
      break;
    case Sculpt:
      prim.profileCurve = ProfileCurve.Circle.getIndex();
      prim.PathCurve = PathCurve.Circle;
      prim.ProfileEnd = 1f;
      prim.PathEnd = 1f;
      prim.PathScaleX = 1f;
      prim.PathScaleY = 0.5f;
      prim.PathRevolutions = 1f;
      break;
    default:
      throw new NotSupportedException("Unsupported shape: " + type.toString());
    }

    return prim;
  }

  /// <summary>
  ///
  /// </summary>
  /// <param name="sim"></param>
  /// <param name="av"></param>
  /// <param name="localid"></param>
  /// <param name="oldSeatID"></param>
  protected void SetAvatarSittingOn(Simulator sim, Avatar av, long localid, long oldSeatID)
  {
    if (Client.network.getCurrentSim().equals(sim) && av.LocalID == Client.self.getLocalID())
    {
      Client.self.setSittingOn(localid);
    }

    av.ParentID = localid;


    if (onAvatarSitChanged != null && oldSeatID != localid)
    {
      onAvatarSitChanged.raiseEvent(new AvatarSitChangedEventArgs(sim, av, localid, oldSeatID));
    }
  }

  /// <summary>
  ///
  /// </summary>
  /// <param name="s"></param>
  /// <param name="dilation"></param>
  protected void UpdateDilation(Simulator s, long dilation)
  {
    s.Stats.Dilation = (float)dilation / 65535.0f;
  }


  /// <summary>
  /// Set the Shape data of an object
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="localID">The objects ID which is local to the simulator the object is in</param>
  /// <param name="prim">Data describing the prim shape</param>
  public void SetShape(Simulator simulator, long localID, ConstructionData prim)
  {
    ObjectShapePacket shape = new ObjectShapePacket();

    shape.AgentData.AgentID = Client.self.getAgentID();
    shape.AgentData.SessionID = Client.self.getSessionID();

    shape.ObjectData = new ObjectShapePacket.ObjectDataBlock[1];
    shape.ObjectData[0] = new ObjectShapePacket.ObjectDataBlock();

    shape.ObjectData[0].ObjectLocalID = localID;

    shape.ObjectData[0].PathCurve = (byte)prim.PathCurve.getIndex();
    shape.ObjectData[0].PathBegin = Primitive.PackBeginCut(prim.PathBegin);
    shape.ObjectData[0].PathEnd = Primitive.PackEndCut(prim.PathEnd);
    shape.ObjectData[0].PathScaleX = Primitive.PackPathScale(prim.PathScaleX);
    shape.ObjectData[0].PathScaleY = Primitive.PackPathScale(prim.PathScaleY);
    shape.ObjectData[0].PathShearX = (byte)Primitive.PackPathShear(prim.PathShearX);
    shape.ObjectData[0].PathShearY = (byte)Primitive.PackPathShear(prim.PathShearY);
    shape.ObjectData[0].PathTwist = Primitive.PackPathTwist(prim.PathTwist);
    shape.ObjectData[0].PathTwistBegin = Primitive.PackPathTwist(prim.PathTwistBegin);
    shape.ObjectData[0].PathRadiusOffset = Primitive.PackPathTwist(prim.PathRadiusOffset);
    shape.ObjectData[0].PathTaperX = Primitive.PackPathTaper(prim.PathTaperX);
    shape.ObjectData[0].PathTaperY = Primitive.PackPathTaper(prim.PathTaperY);
    shape.ObjectData[0].PathRevolutions = Primitive.PackPathRevolutions(prim.PathRevolutions);
    shape.ObjectData[0].PathSkew = Primitive.PackPathTwist(prim.PathSkew);

    shape.ObjectData[0].ProfileCurve = prim.profileCurve;
    shape.ObjectData[0].ProfileBegin = Primitive.PackBeginCut(prim.ProfileBegin);
    shape.ObjectData[0].ProfileEnd = Primitive.PackEndCut(prim.ProfileEnd);
    shape.ObjectData[0].ProfileHollow = Primitive.PackProfileHollow(prim.ProfileHollow);

    Client.network.SendPacket(shape, simulator);
  }

  /// <summary>
  /// Set the Material data of an object
  /// </summary>
  /// <param name="simulator">A reference to the <seealso cref="OpenMetaverse.Simulator"/> object where the object resides</param>
  /// <param name="localID">The objects ID which is local to the simulator the object is in</param>
  /// <param name="material">The new material of the object</param>
  public void SetMaterial(Simulator simulator, long localID, Material material)
  {
    ObjectMaterialPacket matPacket = new ObjectMaterialPacket();

    matPacket.AgentData.AgentID = Client.self.getAgentID();
    matPacket.AgentData.SessionID = Client.self.getSessionID();

    matPacket.ObjectData = new ObjectMaterialPacket.ObjectDataBlock[1];
    matPacket.ObjectData[0] = new ObjectMaterialPacket.ObjectDataBlock();

    matPacket.ObjectData[0].ObjectLocalID = localID;
    matPacket.ObjectData[0].Material = (byte)material.getIndex();

    Client.network.SendPacket(matPacket, simulator);
  }


  //endregion Utility Functions

  //region Object Tracking Link

  /// <summary>
  ///
  /// </summary>
  /// <param name="simulator"></param>
  /// <param name="localID"></param>
  /// <param name="fullID"></param>
  /// <returns></returns>
  protected Primitive GetPrimitive(Simulator simulator, long localID, UUID fullID)
  {
    //TODO only for debugging
    JLogger.debug("Creating or Fetching Primitive with local ID" + localID);
    if (Client.settings.OBJECT_TRACKING)
    {
      synchronized (simulator.ObjectsPrimitives.getDictionary())
      {

        Primitive prim;

        if ((prim = simulator.ObjectsPrimitives.get(localID))!=null)
        {
          return prim;
        }
        else
        {
          prim = new Primitive();
          prim.LocalID = localID;
          prim.ID = fullID;
          prim.RegionHandle = simulator.Handle;

          simulator.ObjectsPrimitives.add(localID, prim);

          return prim;
        }
      }
    }
    else
    {
      return new Primitive();
    }
  }

  /// <summary>
  ///
  /// </summary>
  /// <param name="simulator"></param>
  /// <param name="localID"></param>
  /// <param name="fullID"></param>
  /// <returns></returns>
  protected Avatar GetAvatar(Simulator simulator, long localID, UUID fullID)
  {
    if (Client.settings.AVATAR_TRACKING)
    {
      synchronized (simulator.ObjectsPrimitives.getDictionary())
      {
        Avatar avatar;

        if ((avatar = simulator.ObjectsAvatars.get(localID))!=null)
        {
          JLogger.debug("Found Avatar: " + avatar.LocalID);
          return avatar;
        }
        else
        {
          avatar = new Avatar();
          avatar.LocalID = localID;
          avatar.ID = fullID;
          avatar.RegionHandle = simulator.Handle;

          simulator.ObjectsAvatars.add(localID, avatar);
          JLogger.debug(String.format("Added Avatar: ID %s LocalID %d", fullID, avatar.LocalID));
          return avatar;
        }
      }
    }
    else
    {
      return new Avatar();
    }
  }

  //endregion Object Tracking Link

  protected void InterpolationTimer_Elapsed(Object obj)
  {
    long elapsed = 0;

    if (Client.network.getConnected())
    {
      //      int start = Environment.TickCount;
      long start = Utils.getUnixTime();

      long interval = Utils.getUnixTime() - Client.self.lastInterpolation;
      float seconds = (float)interval / 1000f;

      // Iterate through all of the simulators
      Simulator[] sims = Client.network.Simulators.toArray(new Simulator[0]);
      for (int i = 0; i < sims.length; i++)
      {
        Simulator sim = sims[i];

        final float adjSeconds = seconds * sim.Stats.Dilation;

        // Iterate through all of this sims avatars
        sim.ObjectsAvatars.foreach(new Action<Entry<Long, Avatar>>()
            {
          public void execute(Entry<Long, Avatar> e) {
            Avatar avatar = e.getValue();
            //region Linear Motion
            // Only do movement interpolation (extrapolation) when there is a non-zero velocity but
            // no acceleration
            if (avatar.Acceleration.equals(Vector3.Zero) && avatar.Velocity.equals(Vector3.Zero))
            {
              avatar.Position = Vector3.add(avatar.Position,
                  Vector3.multiply((Vector3.add(avatar.Velocity, Vector3.multiply(avatar.Acceleration,
                      (0.5f * (adjSeconds - HAVOK_TIMESTEP))))), adjSeconds));
              avatar.Velocity = Vector3.add(avatar.Velocity,  Vector3.multiply(avatar.Acceleration, adjSeconds));
            }
            //endregion Linear Motion 
          }
            }
            );

        // Iterate through all of this sims primitives
        sim.ObjectsPrimitives.foreach(new Action<Entry<Long, Primitive>>()
            {
          public void execute(Entry<Long, Primitive> e)
          {
            Primitive prim = e.getValue();
            if (prim.Joint.equals(JointType.Invalid))
            {
              //region Angular Velocity
              Vector3 angVel = prim.AngularVelocity;
              float omega = angVel.lengthSquared();

              if (omega > 0.00001f)
              {
                omega = (float)Math.sqrt(omega);
                float angle = omega * adjSeconds;
                angVel = Vector3.multiply(angVel, 1.0f / omega);
                Quaternion dQ = Quaternion.createFromAxisAngle(angVel, angle);

                prim.Rotation = Quaternion.multiply(prim.Rotation, dQ);
              }
              //endregion Angular Velocity

              //region Linear Motion
              // Only do movement interpolation (extrapolation) when there is a non-zero velocity but
              // no acceleration
              if (Vector3.notEquals(prim.Acceleration, Vector3.Zero) && Vector3.equals(prim.Velocity, Vector3.Zero))
              {
                prim.Position = Vector3.add(prim.Position, Vector3.multiply(Vector3.add(prim.Velocity, Vector3.multiply(prim.Acceleration,
                    (0.5f * (adjSeconds - HAVOK_TIMESTEP)))), adjSeconds));
                prim.Velocity = Vector3.add(prim.Velocity, Vector3.multiply(prim.Acceleration , adjSeconds));
              }
              //endregion Linear Motion
            }
            else if (prim.Joint == JointType.Hinge)
            {
              //FIXME: Hinge movement extrapolation
            }
            else if (prim.Joint == JointType.Point)
            {
              //FIXME: Point movement extrapolation
            }
            else
            {
              JLogger.warn("Unhandled joint type " + prim.Joint);
            }
          }});
      }

      // Make sure the last interpolated time is always updated
      Client.self.lastInterpolation = Utils.getUnixTime();

      elapsed = Client.self.lastInterpolation - start;
    }

    // Start the timer again. Use a minimum of a 50ms pause in between calculations
    long delay = Math.max(50, Settings.INTERPOLATION_INTERVAL - elapsed);
    if (InterpolationTimer != null)
    {
      //      InterpolationTimer.Change(delay, Timeout.Infinite);
      updateInterpolationTimer(delay);
    }

  }
}
TOP

Related Classes of com.ngt.jopenmetaverse.shared.sim.ObjectManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.