Files
libremetaverse/libsecondlife-cs/ObjectManager.cs
John Hurliman 8ac9a090bf * Calculates the W value of the normalized rotation quaternion in ObjectUpdates
* CompressedUpdateHandler callback added
* Added function comments
* Registered events for the rest of the object packets

git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@193 52acb1d6-8a22-11de-b505-999d5b087335
2006-09-16 03:15:27 +00:00

491 lines
22 KiB
C#

/*
* Copyright (c) 2006, Second Life Reverse Engineering Team
* All rights reserved.
*
* - Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Neither the name of the Second Life Reverse Engineering Team nor the names
* of its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections;
namespace libsecondlife
{
public delegate void NewPrimCallback(Simulator simulator, PrimObject prim, U64 regionHandle, ushort timeDilation);
public delegate void NewAvatarCallback(Simulator simulator, Avatar avatar, U64 regionHandle, ushort timeDilation);
public delegate void PrimMovedCallback(Simulator simulator, PrimUpdate prim, U64 regionHandle, ushort timeDilation);
public delegate void AvatarMovedCallback(Simulator simulator, AvatarUpdate avatar, U64 regionHandle, ushort timeDilation);
/// <summary>
/// Contains all of the variables sent in an object update packet for a
/// prim object. Used to track position and movement of prims.
/// </summary>
public struct PrimUpdate
{
public uint LocalID;
public byte State;
public LLVector3 Position;
public LLVector3 Velocity;
public LLVector3 Acceleration;
public LLQuaternion Rotation;
public LLVector3 RotationVelocity;
}
/// <summary>
/// Contains all of the variables sent in an object update packet for an
/// avatar. Used to track position and movement of avatars.
/// </summary>
public struct AvatarUpdate
{
public uint LocalID;
public byte State;
public LLVector4 CollisionPlane;
public LLVector3 Position;
public LLVector3 Velocity;
public LLVector3 Acceleration;
public LLQuaternion Rotation;
public LLVector3 RotationVelocity;
}
/// <summary>
/// Handles all network traffic related to prims and avatar positions and
/// movement.
/// </summary>
public class ObjectManager
{
/// <summary>
/// This event will be raised for every ObjectUpdate block that
/// contains a new prim. Depending on the circumstances a client could
/// receive two or more of these events for the same object, if you
/// or the object left the current sim and returned for example. Client
/// applications are responsible for tracking and storing objects.
/// </summary>
public event NewPrimCallback OnNewPrim;
/// <summary>
/// This event will be raised for every ObjectUpdate block that
/// contains a new avatar. Depending on the circumstances a client
/// could receive two or more of these events for the same avatar, if
/// you or the other avatar left the current sim and returned for
/// example. Client applications are responsible for tracking and
/// storing objects.
/// </summary>
public event NewAvatarCallback OnNewAvatar;
/// <summary>
/// This event will be raised when a prim movement packet is received,
/// containing the updated position, rotation, and movement-related
/// vectors.
/// </summary>
public event PrimMovedCallback OnPrimMoved;
/// <summary>
/// This event will be raised when an avatar movement packet is
/// received, containing the updated position, rotation, and
/// movement-related vectors.
/// </summary>
public event AvatarMovedCallback OnAvatarMoved;
protected SecondLife Client;
public ObjectManager(SecondLife client)
{
Client = client;
Client.Network.RegisterCallback("ObjectUpdate", new PacketCallback(UpdateHandler));
Client.Network.RegisterCallback("ImprovedTerseObjectUpdate", new PacketCallback(TerseUpdateHandler));
Client.Network.RegisterCallback("ObjectUpdateCompressed", new PacketCallback(CompressedUpdateHandler));
Client.Network.RegisterCallback("ObjectUpdateCached", new PacketCallback(CachedUpdateHandler));
}
private void UpdateHandler(Packet packet, Simulator simulator)
{
U64 regionHandle = null;
ushort timeDilation = 0;
foreach (Block block in packet.Blocks())
{
Avatar avatar = null;
PrimObject prim = new PrimObject();
foreach (Field field in block.Fields)
{
switch (field.Layout.Name)
{
case "ID":
prim.LocalID = (UInt32)field.Data;
break;
case "State":
prim.State = (byte)field.Data;
break;
case "FullID":
prim.ID = (LLUUID)field.Data;
break;
case "ParentID":
// Linked objects?
break;
case "OwnerID":
// Sound-related
break;
case "Material":
prim.Material = (byte)(field.Data);
break;
case "PathCurve":
prim.PathCurve = (byte)field.Data;
break;
case "ProfileCurve":
prim.ProfileCurve = (byte)field.Data;
break;
case "PathBegin":
prim.PathBegin = PrimObject.PathBeginFloat((byte)field.Data);
break;
case "PathEnd":
prim.PathEnd = PrimObject.PathEndFloat((byte)field.Data);
break;
case "PathScaleX":
prim.PathScaleX = PrimObject.PathScaleFloat((byte)field.Data);
break;
case "PathScaleY":
prim.PathScaleY = PrimObject.PathScaleFloat((byte)field.Data);
break;
case "PathShearX":
prim.PathShearX = PrimObject.PathShearFloat((byte)field.Data);
break;
case "PathShearY":
prim.PathShearY = PrimObject.PathShearFloat((byte)field.Data);
break;
case "PathTwist":
prim.PathTwist = PrimObject.PathTwistFloat((sbyte)field.Data);
break;
case "PathTwistBegin":
prim.PathTwistBegin = PrimObject.PathTwistFloat((sbyte)field.Data);
break;
case "PathRadiusOffset":
prim.PathRadiusOffset = PrimObject.PathRadiusOffsetFloat((sbyte)field.Data);
break;
case "PathTaperX":
prim.PathTaperX = PrimObject.PathTaperFloat((byte)(sbyte)field.Data);
break;
case "PathTaperY":
prim.PathTaperY = PrimObject.PathTaperFloat((byte)(sbyte)field.Data);
break;
case "PathRevolutions":
prim.PathRevolutions = PrimObject.PathRevolutionsFloat((byte)field.Data);
break;
case "PathSkew":
prim.PathSkew = PrimObject.PathSkewFloat((byte)(sbyte)field.Data);
break;
case "ProfileBegin":
prim.ProfileBegin = PrimObject.ProfileBeginFloat((byte)field.Data);
break;
case "ProfileEnd":
prim.ProfileEnd = PrimObject.ProfileEndFloat((byte)field.Data);
break;
case "ProfileHollow":
prim.ProfileHollow = (byte)field.Data;
break;
case "NameValue":
Console.WriteLine("[debug] Name: " + Helpers.FieldToString(field.Data));
prim.Name = Helpers.FieldToString(field.Data);
break;
case "Data":
// ?
break;
case "Text":
// Hovering text
break;
case "TextColor":
// LLColor4U of the hovering text
break;
case "MediaURL":
// Quicktime stream
Client.Log("MediaURL: " + Helpers.FieldToString(field.Data), Helpers.LogLevel.Info);
break;
case "TextureEntry":
// TODO: Multi-texture support
byte[] bytes = (byte[])field.Data;
prim.Texture = new LLUUID(bytes, 0);
break;
case "TextureAnim":
// Not sure how this works
break;
case "JointType":
// ?
break;
case "JointPivot":
// ?
break;
case "JointAxisOrAnchor":
// ?
break;
case "PCode":
// ?
break;
case "PSBlock":
// Particle system related
break;
case "ExtraParams":
// ?
break;
case "Scale":
prim.Scale = (LLVector3)field.Data;
break;
case "Flags":
// ?
break;
case "UpdateFlags":
// ?
break;
case "CRC":
// We could optionally verify this on the client side
break;
case "ClickAction":
//
break;
case "Gain":
// Sound-related
break;
case "Sound":
// Sound-related
break;
case "Radius":
// Sound-related
break;
case "ObjectData":
byte[] data = (byte[])field.Data;
if (data.Length == 60)
{
prim.Position = new LLVector3(data, 0);
prim.Rotation = new LLQuaternion(data, 36);
// Calculate the quaternion W value from the given X/Y/Z
float xyzsum = 1.0F -
prim.Rotation.X * prim.Rotation.X -
prim.Rotation.Y * prim.Rotation.Y -
prim.Rotation.Z * prim.Rotation.Z;
prim.Rotation.S = (xyzsum > 0.0F) ? (float)Math.Sqrt(xyzsum) : 0.0F;
// TODO: Parse the rest of the fields
}
// TODO: Parse ObjectData for avatars
break;
case "TimeDilation":
timeDilation = (ushort)field.Data;
break;
case "RegionHandle":
regionHandle = (U64)field.Data;
break;
default:
Client.Log("ObjectUpdate field not handled: " + field.Layout.Name + " " +
field.Data.GetType().ToString(), Helpers.LogLevel.Info);
break;
}
}
// Parse the NameValue to see if this is actually an avatar
if (prim.LocalID != 0)
{
if (prim.Name.Contains("FirstName"))
{
avatar = new Avatar();
avatar.ID = prim.ID;
avatar.LocalID = prim.LocalID;
// FIXME: Parse the correct name and group name
avatar.Name = prim.Name;
avatar.GroupName = prim.Name;
avatar.Online = true;
avatar.Position = prim.Position;
// TODO: Look up the region by regionHandle instead
avatar.CurrentRegion = simulator.Region;
prim = null;
// If an event handler is registered call it
if (OnNewAvatar != null)
{
OnNewAvatar(simulator, avatar, regionHandle, timeDilation);
}
}
else
{
// If an event handler is registered call it
if (OnNewPrim != null)
{
OnNewPrim(simulator, prim, regionHandle, timeDilation);
}
}
}
}
}
private void TerseUpdateHandler(Packet packet, Simulator simulator)
{
U64 regionHandle = null;
ushort timeDilation = 0;
bool avatar = false;
int i;
byte[] data;
uint localid = 0;
byte state = 0;
float x, y, z, s;
LLVector4 CollisionPlane = null;
LLVector3 Position = null, Velocity = null, Acceleration = null, RotationVelocity = null;
LLQuaternion Rotation = null;
foreach (Block block in packet.Blocks())
{
foreach (Field field in block.Fields)
{
switch (field.Layout.Name)
{
case "Data":
i = 0;
data = (byte[])field.Data;
// Region ID
localid = (uint)(data[i] + (data[i + 1] << 8) + (data[i + 2] << 16) + (data[i + 3] << 24));
i += 4;
// Object state
state = data[i++];
// Avatar boolean
avatar = Convert.ToBoolean(data[i]);
i++;
if (avatar)
{
CollisionPlane = new LLVector4(data, i);
i += 16;
}
// Position
Position = new LLVector3(data, i);
i += 12;
// Velocity
x = Dequantize(data, i, -128.0F, 128.0F);
i += 2;
y = Dequantize(data, i, -128.0F, 128.0F);
i += 2;
z = Dequantize(data, i, -128.0F, 128.0F);
i += 2;
Velocity = new LLVector3(x, y, z);
// Acceleration
x = Dequantize(data, i, -64.0F, 64.0F);
i += 2;
y = Dequantize(data, i, -64.0F, 64.0F);
i += 2;
z = Dequantize(data, i, -64.0F, 64.0F);
i += 2;
Acceleration = new LLVector3(x, y, z);
// Rotation
x = Dequantize(data, i, -1.0F, 1.0F);
i += 2;
y = Dequantize(data, i, -1.0F, 1.0F);
i += 2;
z = Dequantize(data, i, -1.0F, 1.0F);
i += 2;
s = Dequantize(data, i, -1.0F, 1.0F);
i += 2;
Rotation = new LLQuaternion(x, y, z, s);
// Rotation velocity
x = Dequantize(data, i, -64.0F, 64.0F);
i += 2;
y = Dequantize(data, i, -64.0F, 64.0F);
i += 2;
z = Dequantize(data, i, -64.0F, 64.0F);
i += 2;
RotationVelocity = new LLVector3(x, y, z);
break;
case "RegionHandle":
regionHandle = (U64)field.Data;
break;
case "TimeDilation":
timeDilation = (ushort)field.Data;
break;
case "TextureEntry":
//
break;
}
}
}
if (avatar)
{
AvatarUpdate avupdate = new AvatarUpdate();
avupdate.LocalID = localid;
avupdate.State = state;
avupdate.Position = Position;
avupdate.CollisionPlane = CollisionPlane;
avupdate.Velocity = Velocity;
avupdate.Acceleration = Acceleration;
avupdate.Rotation = Rotation;
avupdate.RotationVelocity = RotationVelocity;
/*Client.Log("AVATAR: LocalID: " + localid + ", State: " + state + ", Position: " + Position.ToString() +
", CollisionPlane: " + CollisionPlane.ToString() + ", Velocity: " + Velocity.ToString() +
", Acceleration: " + Acceleration.ToString() + ", Rotation: " + Rotation.ToString() +
", RotationVelocity: " + RotationVelocity.ToString(), Helpers.LogLevel.Info);*/
// If an event handler is registered call it
}
else
{
/*Client.Log("PRIM: LocalID: " + localid + ", State: " + state + ", Position: " + Position.ToString() +
", Velocity: " + Velocity.ToString() + ", Acceleration: " + Acceleration.ToString() +
", Rotation: " + Rotation.ToString() + ", RotationVelocity: " + RotationVelocity.ToString(),
Helpers.LogLevel.Info);*/
// If an event handler is registered call it
}
}
private void CompressedUpdateHandler(Packet packet, Simulator simulator)
{
Client.Log("Received an ObjectUpdateCompressed packet, length=" + packet.Data.Length, Helpers.LogLevel.Info);
}
private void CachedUpdateHandler(Packet packet, Simulator simulator)
{
Client.Log("Received an ObjectUpdateCached packet, length=" + packet.Data.Length, Helpers.LogLevel.Info);
}
/// <summary>
/// Takes a quantized 16-bit value from a byte array and its range and returns
/// a float representation of the continuous value. For example, a value of
/// 32767 and a range of -128.0 to 128.0 would return 0.0. The endian conversion
/// from the 16-bit little endian to the native platform will also be handled.
/// </summary>
/// <param name="byteArray">The byte array containing the short value</param>
/// <param name="pos">The beginning position of the short (quantized) value</param>
/// <param name="lower">The lower quantization range</param>
/// <param name="upper">The upper quantization range</param>
/// <returns>A 32-bit floating point representation of the dequantized value</returns>
private float Dequantize(byte[] byteArray, int pos, float lower, float upper)
{
ushort value = (ushort)(byteArray[pos] + (byteArray[pos + 1] << 8));
float QV = (float)value;
float range = upper - lower;
float QF = range / 65536.0F;
return (float)((QV * QF - (0.5F * range)) + QF);
}
}
}