/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * * 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. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the OpenSimulator Project 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 DEVELOPERS ``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 CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Reflection; using System.Runtime.Serialization; using System.Security.Permissions; using System.Xml; using System.Xml.Serialization; using log4net; using OpenMetaverse; using OpenMetaverse.Packets; using OpenMetaverse.StructuredData; using OpenSim.Framework; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes.Scripting; using OpenSim.Region.Framework.Scenes.Serialization; using OpenSim.Region.PhysicsModules.SharedBase; using PermissionMask = OpenSim.Framework.PermissionMask; namespace OpenSim.Region.Framework.Scenes { #region Enumerations [Flags] public enum Changed : uint { INVENTORY = 1, COLOR = 2, SHAPE = 4, SCALE = 8, TEXTURE = 16, LINK = 32, ALLOWED_DROP = 64, OWNER = 128, REGION = 256, TELEPORT = 512, REGION_RESTART = 1024, MEDIA = 2048, ANIMATION = 16384, POSITION = 32768 } // I don't really know where to put this except here. // Can't access the OpenSim.Region.ScriptEngine.Common.LSL_BaseClass.Changed constants [Flags] public enum ExtraParamType { Something1 = 1, Something2 = 2, Something3 = 4, Something4 = 8, Flexible = 16, Light = 32, Sculpt = 48, Something5 = 64, Something6 = 128 } [Flags] public enum TextureAnimFlags : byte { NONE = 0x00, ANIM_ON = 0x01, LOOP = 0x02, REVERSE = 0x04, PING_PONG = 0x08, SMOOTH = 0x10, ROTATE = 0x20, SCALE = 0x40 } public enum PrimType : int { BOX = 0, CYLINDER = 1, PRISM = 2, SPHERE = 3, TORUS = 4, TUBE = 5, RING = 6, SCULPT = 7 } public enum UpdateRequired : byte { NONE = 0, TERSE = 1, FULL = 2 } #endregion Enumerations public class SceneObjectPart : ISceneEntity { /// /// Denote all sides of the prim /// public const int ALL_SIDES = -1; private const scriptEvents PhysicsNeededSubsEvents = ( scriptEvents.collision | scriptEvents.collision_start | scriptEvents.collision_end | scriptEvents.land_collision | scriptEvents.land_collision_start | scriptEvents.land_collision_end ); private const scriptEvents PhyscicsPhantonSubsEvents = ( scriptEvents.land_collision | scriptEvents.land_collision_start | scriptEvents.land_collision_end ); private const scriptEvents PhyscicsVolumeDtcSubsEvents = ( scriptEvents.collision_start | scriptEvents.collision_end ); private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); /// /// Dynamic attributes can be created and deleted as required. /// public DAMap DynAttrs { get; set; } private DOMap m_dynObjs; /// /// Dynamic objects that can be created and deleted as required. /// public DOMap DynObjs { get { if (m_dynObjs == null) m_dynObjs = new DOMap(); return m_dynObjs; } set { m_dynObjs = value; } } /// /// Is this a root part? /// /// /// This will return true even if the whole object is attached to an avatar. /// public bool IsRoot { get { return Object.ReferenceEquals(ParentGroup.RootPart, this); } } /// /// Is an explicit sit target set for this part? /// public bool IsSitTargetSet { get { return !(SitTargetPosition == Vector3.Zero && (SitTargetOrientation == Quaternion.Identity // Valid Zero Rotation quaternion || SitTargetOrientation.X == 0f && SitTargetOrientation.Y == 0f && SitTargetOrientation.Z == 1f && SitTargetOrientation.W == 0f // W-Z Mapping was invalid at one point || SitTargetOrientation.X == 0f && SitTargetOrientation.Y == 0f && SitTargetOrientation.Z == 0f && SitTargetOrientation.W == 0f)); // Invalid Quaternion } } #region Fields public bool AllowedDrop; public bool DIE_AT_EDGE; public bool RETURN_AT_EDGE; public bool BlockGrab { get; set; } public bool StatusSandbox; public Vector3 StatusSandboxPos; [XmlIgnore] public int[] PayPrice = {-2,-2,-2,-2,-2}; [XmlIgnore] /// /// The representation of this part in the physics scene. /// /// /// If you use this property more than once in a section of code then you must take a reference and use that. /// If another thread is simultaneously turning physics off on this part then this refernece could become /// null at any time. /// public PhysicsActor PhysActor { get; set; } //Xantor 20080528 Sound stuff: // Note: This isn't persisted in the database right now, as the fields for that aren't just there yet. // Not a big problem as long as the script that sets it remains in the prim on startup. // for SL compatibility it should be persisted though (set sound / displaytext / particlesystem, kill script) public UUID Sound; public byte SoundFlags; public double SoundGain; public double SoundRadius; /// /// Should sounds played from this prim be queued? /// /// /// This should only be changed by sound modules. It is up to sound modules as to how they interpret this setting. /// public bool SoundQueueing { get; set; } public uint TimeStampFull; public uint TimeStampLastActivity; // Will be used for AutoReturn public uint TimeStampTerse; // The following two are to hold the attachment data // while an object is inworld [XmlIgnore] public byte AttachPoint = 0; [XmlIgnore] public Quaternion AttachRotation = Quaternion.Identity; [XmlIgnore] public int STATUS_ROTATE_X; public int STATUS_ROTATE_Y; public int STATUS_ROTATE_Z; private Dictionary m_CollisionFilter = new Dictionary(); /// /// The UUID of the user inventory item from which this object was rezzed if this is a root part. /// If UUID.Zero then either this is not a root part or there is no connection with a user inventory item. /// private UUID m_fromUserInventoryItemID; public UUID FromUserInventoryItemID { get { return m_fromUserInventoryItemID; } set { m_fromUserInventoryItemID = value; } } public scriptEvents AggregateScriptEvents; public Vector3 AttachedPos; public Vector3 RotationAxis = Vector3.One; public bool VolumeDetectActive; public bool IsWaitingForFirstSpinUpdatePacket; public Quaternion SpinOldOrientation = Quaternion.Identity; protected bool m_APIDActive = false; protected Quaternion m_APIDTarget = Quaternion.Identity; protected float m_APIDDamp = 0; protected float m_APIDStrength = 0; /// /// This part's inventory /// public IEntityInventory Inventory { get { return m_inventory; } } protected SceneObjectPartInventory m_inventory; public bool Undoing; public bool IgnoreUndoUpdate = false; public PrimFlags LocalFlags; private float m_damage = -1.0f; private byte[] m_TextureAnimation; private byte m_clickAction; private Color m_color = Color.Black; private readonly List m_lastColliders = new List(); private int m_linkNum; private int m_scriptAccessPin; private readonly Dictionary m_scriptEvents = new Dictionary(); private string m_sitName = String.Empty; private Quaternion m_sitTargetOrientation = Quaternion.Identity; private Vector3 m_sitTargetPosition; private string m_sitAnimation = "SIT"; private bool m_occupied; // KF if any av is sitting on this prim private string m_text = String.Empty; private string m_touchName = String.Empty; private UndoRedoState m_UndoRedo = null; private bool m_passTouches = false; private bool m_passCollisions = false; protected Vector3 m_acceleration; protected Vector3 m_angularVelocity; //unkown if this will be kept, added as a way of removing the group position from the group class protected Vector3 m_groupPosition; protected uint m_localId; protected Material m_material = OpenMetaverse.Material.Wood; protected string m_name; protected Vector3 m_offsetPosition; protected SceneObjectGroup m_parentGroup; protected byte[] m_particleSystem = Utils.EmptyBytes; protected ulong m_regionHandle; protected Quaternion m_rotationOffset = Quaternion.Identity; protected PrimitiveBaseShape m_shape; protected UUID m_uuid; protected Vector3 m_velocity; protected Vector3 m_lastPosition; protected Quaternion m_lastRotation; protected Vector3 m_lastVelocity; protected Vector3 m_lastAcceleration; protected Vector3 m_lastAngularVelocity; protected int m_lastUpdateSentTime; protected float m_buoyancy = 0.0f; protected Vector3 m_force; protected Vector3 m_torque; protected byte m_physicsShapeType = (byte)PhysShapeType.prim; protected float m_density = 1000.0f; // in kg/m^3 protected float m_gravitymod = 1.0f; protected float m_friction = 0.6f; // wood protected float m_bounce = 0.5f; // wood protected bool m_isSelected = false; /// /// Stores media texture data /// protected string m_mediaUrl; // TODO: Those have to be changed into persistent properties at some later point, // or sit-camera on vehicles will break on sim-crossing. private Vector3 m_cameraEyeOffset; private Vector3 m_cameraAtOffset; private bool m_forceMouselook; // 0 for default collision sounds, -1 for script disabled sound 1 for script defined sound private sbyte m_collisionSoundType; private UUID m_collisionSound; private float m_collisionSoundVolume; private int LastColSoundSentTime; private SOPVehicle m_vehicleParams = null; public KeyframeMotion KeyframeMotion { get; set; } #endregion Fields // ~SceneObjectPart() // { // Console.WriteLine( // "[SCENE OBJECT PART]: Destructor called for {0}, local id {1}, parent {2} {3}", // Name, LocalId, ParentGroup.Name, ParentGroup.LocalId); // m_log.DebugFormat( // "[SCENE OBJECT PART]: Destructor called for {0}, local id {1}, parent {2} {3}", // Name, LocalId, ParentGroup.Name, ParentGroup.LocalId); // } #region Constructors /// /// No arg constructor called by region restore db code /// public SceneObjectPart() { m_TextureAnimation = Utils.EmptyBytes; m_particleSystem = Utils.EmptyBytes; Rezzed = DateTime.UtcNow; Description = String.Empty; DynAttrs = new DAMap(); // Prims currently only contain a single folder (Contents). From looking at the Second Life protocol, // this appears to have the same UUID (!) as the prim. If this isn't the case, one can't drag items from // the prim into an agent inventory (Linden client reports that the "Object not found for drop" in its log m_inventory = new SceneObjectPartInventory(this); LastColSoundSentTime = Util.EnvironmentTickCount(); } /// /// Create a completely new SceneObjectPart (prim). This will need to be added separately to a SceneObjectGroup /// /// /// /// /// /// public SceneObjectPart( UUID ownerID, PrimitiveBaseShape shape, Vector3 groupPosition, Quaternion rotationOffset, Vector3 offsetPosition) : this() { m_name = "Object"; CreationDate = (int)Utils.DateTimeToUnixTime(Rezzed); LastOwnerID = CreatorID = OwnerID = ownerID; UUID = UUID.Random(); Shape = shape; OwnershipCost = 0; ObjectSaleType = 0; SalePrice = 0; Category = 0; GroupPosition = groupPosition; OffsetPosition = offsetPosition; RotationOffset = rotationOffset; Velocity = Vector3.Zero; AngularVelocity = Vector3.Zero; Acceleration = Vector3.Zero; Flags = 0; CreateSelected = true; TrimPermissions(); } #endregion Constructors #region XML Schema private UUID _lastOwnerID; private UUID _ownerID; private UUID _groupID; private int _ownershipCost; private byte _objectSaleType; private int _salePrice; private uint _category; private Int32 _creationDate; private uint _parentID = 0; private uint _baseMask = (uint)(PermissionMask.All | PermissionMask.Export); private uint _ownerMask = (uint)(PermissionMask.All | PermissionMask.Export); private uint _groupMask = (uint)PermissionMask.None; private uint _everyoneMask = (uint)PermissionMask.None; private uint _nextOwnerMask = (uint)(PermissionMask.Move | PermissionMask.Modify | PermissionMask.Transfer); private PrimFlags _flags = PrimFlags.None; private DateTime m_expires; private DateTime m_rezzed; private bool m_createSelected = false; private UUID _creatorID; public UUID CreatorID { get { return _creatorID; } set { _creatorID = value; } } private string m_creatorData = string.Empty; /// /// Data about the creator in the form home_url;name /// public string CreatorData { get { return m_creatorData; } set { m_creatorData = value; } } /// /// Used by the DB layer to retrieve / store the entire user identification. /// The identification can either be a simple UUID or a string of the form /// uuid[;home_url[;name]] /// public string CreatorIdentification { get { if (!string.IsNullOrEmpty(CreatorData)) return CreatorID.ToString() + ';' + CreatorData; else return CreatorID.ToString(); } set { if ((value == null) || (value != null && value == string.Empty)) { CreatorData = string.Empty; return; } if (!value.Contains(";")) // plain UUID { UUID uuid = UUID.Zero; UUID.TryParse(value, out uuid); CreatorID = uuid; } else // [;[;name]] { string name = "Unknown User"; string[] parts = value.Split(';'); if (parts.Length >= 1) { UUID uuid = UUID.Zero; UUID.TryParse(parts[0], out uuid); CreatorID = uuid; } if (parts.Length >= 2) { CreatorData = parts[1]; if (!CreatorData.EndsWith("/")) CreatorData += "/"; } if (parts.Length >= 3) name = parts[2]; CreatorData += ';' + name; } } } /// /// A relic from when we we thought that prims contained folder objects. In /// reality, prim == folder /// Exposing this is not particularly good, but it's one of the least evils at the moment to see /// folder id from prim inventory item data, since it's not (yet) actually stored with the prim. /// public UUID FolderID { get { return UUID; } set { } // Don't allow assignment, or legacy prims wil b0rk - but we need the setter for legacy serialization. } /// /// Access should be via Inventory directly - this property temporarily remains for xml serialization purposes /// public uint InventorySerial { get { return m_inventory.Serial; } set { m_inventory.Serial = value; } } /// /// Get the inventory list /// public TaskInventoryDictionary TaskInventory { get { return m_inventory.Items; } set { m_inventory.Items = value; } } /// /// This is idential to the Flags property, except that the returned value is uint rather than PrimFlags /// [Obsolete("Use Flags property instead")] public uint ObjectFlags { get { return (uint)Flags; } set { Flags = (PrimFlags)value; } } public UUID UUID { get { return m_uuid; } set { m_uuid = value; // This is necessary so that TaskInventoryItem parent ids correctly reference the new uuid of this part if (Inventory != null) Inventory.ResetObjectID(); } } public uint LocalId { get { return m_localId; } set { m_localId = value; // m_log.DebugFormat("[SCENE OBJECT PART]: Set part {0} to local id {1}", Name, m_localId); } } public virtual string Name { get { return m_name; } set { m_name = value; PhysicsActor pa = PhysActor; if (pa != null) pa.SOPName = value; } } [XmlIgnore] public bool PassTouches { get { return m_passTouches; } set { m_passTouches = value; if (ParentGroup != null) ParentGroup.HasGroupChanged = true; } } public bool PassCollisions { get { return m_passCollisions; } set { m_passCollisions = value; if (ParentGroup != null) ParentGroup.HasGroupChanged = true; } } public bool IsSelected { get { return m_isSelected; } set { m_isSelected = value; if (ParentGroup != null) ParentGroup.PartSelectChanged(value); } } public Dictionary CollisionFilter { get { return m_CollisionFilter; } set { m_CollisionFilter = value; } } protected bool APIDActive { get { return m_APIDActive; } set { m_APIDActive = value; } } protected Quaternion APIDTarget { get { return m_APIDTarget; } set { m_APIDTarget = value; } } protected float APIDDamp { get { return m_APIDDamp; } set { m_APIDDamp = value; } } protected float APIDStrength { get { return m_APIDStrength; } set { m_APIDStrength = value; } } public ulong RegionHandle { get { return m_regionHandle; } set { m_regionHandle = value; } } public int ScriptAccessPin { get { return m_scriptAccessPin; } set { m_scriptAccessPin = (int)value; } } private SceneObjectPart m_PlaySoundMasterPrim = null; public SceneObjectPart PlaySoundMasterPrim { get { return m_PlaySoundMasterPrim; } set { m_PlaySoundMasterPrim = value; } } private List m_PlaySoundSlavePrims = new List(); public List PlaySoundSlavePrims { get { return m_PlaySoundSlavePrims; } set { m_PlaySoundSlavePrims = value; } } private SceneObjectPart m_LoopSoundMasterPrim = null; public SceneObjectPart LoopSoundMasterPrim { get { return m_LoopSoundMasterPrim; } set { m_LoopSoundMasterPrim = value; } } private List m_LoopSoundSlavePrims = new List(); public List LoopSoundSlavePrims { get { return m_LoopSoundSlavePrims; } set { m_LoopSoundSlavePrims = value; } } public Byte[] TextureAnimation { get { return m_TextureAnimation; } set { m_TextureAnimation = value; } } public Byte[] ParticleSystem { get { return m_particleSystem; } set { m_particleSystem = value; } } public DateTime Expires { get { return m_expires; } set { m_expires = value; } } public DateTime Rezzed { get { return m_rezzed; } set { m_rezzed = value; } } public float Damage { get { return m_damage; } set { m_damage = value; } } public void setGroupPosition(Vector3 pos) { m_groupPosition = pos; } /// /// The position of the entire group that this prim belongs to. /// /// public Vector3 GroupPosition { get { // If this is a linkset, we don't want the physics engine mucking up our group position here. PhysicsActor actor = PhysActor; if (ParentID == 0) { if (actor != null) m_groupPosition = actor.Position; return m_groupPosition; } // If I'm an attachment, my position is reported as the position of who I'm attached to if (ParentGroup.IsAttachment) { ScenePresence sp = ParentGroup.Scene.GetScenePresence(ParentGroup.AttachedAvatar); if (sp != null) return sp.AbsolutePosition; } // use root prim's group position. Physics may have updated it if (ParentGroup.RootPart != this) m_groupPosition = ParentGroup.RootPart.GroupPosition; return m_groupPosition; } set { m_groupPosition = value; PhysicsActor actor = PhysActor; if (actor != null && ParentGroup.Scene.PhysicsScene != null) { try { // Root prim actually goes at Position if (ParentID == 0) { actor.Position = value; } else { // The physics engine always sees all objects (root or linked) in world coordinates. actor.Position = GetWorldPosition(); actor.Orientation = GetWorldRotation(); } // Tell the physics engines that this prim changed. if (ParentGroup != null && ParentGroup.Scene != null && ParentGroup.Scene.PhysicsScene != null) ParentGroup.Scene.PhysicsScene.AddPhysicsActorTaint(actor); } catch (Exception e) { m_log.ErrorFormat("[SCENEOBJECTPART]: GROUP POSITION. {0}", e); } } } } public void setOffsetPosition(Vector3 pos) { m_offsetPosition = pos; } public Vector3 OffsetPosition { get { return m_offsetPosition; } set { Vector3 oldpos = m_offsetPosition; m_offsetPosition = value; if (ParentGroup != null && !ParentGroup.IsDeleted) { PhysicsActor actor = PhysActor; if (ParentID != 0 && actor != null) { actor.Position = GetWorldPosition(); actor.Orientation = GetWorldRotation(); // Tell the physics engines that this prim changed. if (ParentGroup.Scene != null && ParentGroup.Scene.PhysicsScene != null) ParentGroup.Scene.PhysicsScene.AddPhysicsActorTaint(actor); } if (!m_parentGroup.m_dupeInProgress) { List avs = ParentGroup.GetSittingAvatars(); foreach (ScenePresence av in avs) { if (av.ParentID == m_localId) { Vector3 offset = (m_offsetPosition - oldpos); av.AbsolutePosition += offset; // av.SendAvatarDataToAllAgents(); av.SendTerseUpdateToAllClients(); } } } } TriggerScriptChangedEvent(Changed.POSITION); } } public Vector3 RelativePosition { get { if (IsRoot) { if (ParentGroup.IsAttachment) return AttachedPos; else return AbsolutePosition; } else { return OffsetPosition; } } } public void setRotationOffset(Quaternion q) { m_rotationOffset = q; } public Quaternion RotationOffset { get { // We don't want the physics engine mucking up the rotations in a linkset PhysicsActor actor = PhysActor; // If this is a root of a linkset, the real rotation is what the physics engine thinks. // If not a root prim, the offset rotation is computed by SOG and is relative to the root. if (ParentID == 0 && (Shape.PCode != 9 || Shape.State == 0) && actor != null) { if (actor.Orientation.X != 0f || actor.Orientation.Y != 0f || actor.Orientation.Z != 0f || actor.Orientation.W != 0f) { m_rotationOffset = actor.Orientation; } } // float roll, pitch, yaw = 0; // m_rotationOffset.GetEulerAngles(out roll, out pitch, out yaw); // // m_log.DebugFormat( // "[SCENE OBJECT PART]: Got euler {0} for RotationOffset on {1} {2}", // new Vector3(roll, pitch, yaw), Name, LocalId); return m_rotationOffset; } set { // StoreUndoState(); m_rotationOffset = value; PhysicsActor actor = PhysActor; if (actor != null) { try { // Root prim gets value directly if (ParentID == 0) { actor.Orientation = value; //m_log.Info("[PART]: RO1:" + actor.Orientation.ToString()); } else { // Child prim we have to calculate it's world rotationwel Quaternion resultingrotation = GetWorldRotation(); actor.Orientation = resultingrotation; //m_log.Info("[PART]: RO2:" + actor.Orientation.ToString()); } if (ParentGroup != null && ParentGroup.Scene != null && ParentGroup.Scene.PhysicsScene != null) ParentGroup.Scene.PhysicsScene.AddPhysicsActorTaint(actor); //} } catch (Exception ex) { m_log.Error("[SCENEOBJECTPART]: ROTATIONOFFSET" + ex.Message); } } // float roll, pitch, yaw = 0; // m_rotationOffset.GetEulerAngles(out roll, out pitch, out yaw); // // m_log.DebugFormat( // "[SCENE OBJECT PART]: Set euler {0} for RotationOffset on {1} {2}", // new Vector3(roll, pitch, yaw), Name, LocalId); } } /// public Vector3 Velocity { get { PhysicsActor actor = PhysActor; if (actor != null) { if (actor.IsPhysical) { m_velocity = actor.Velocity; } } return m_velocity; } set { if (Util.IsNanOrInfinity(value)) m_velocity = Vector3.Zero; else m_velocity = value; PhysicsActor actor = PhysActor; if (actor != null) { if (actor.IsPhysical) { actor.Velocity = m_velocity; ParentGroup.Scene.PhysicsScene.AddPhysicsActorTaint(actor); } } } } /// Update angular velocity and schedule terse update. public void UpdateAngularVelocity(Vector3 avel) { AngularVelocity = avel; ScheduleTerseUpdate(); ParentGroup.HasGroupChanged = true; } /// Get or set angular velocity. Does not schedule update. public Vector3 AngularVelocity { get { PhysicsActor actor = PhysActor; if ((actor != null) && actor.IsPhysical && ParentGroup.RootPart == this) { m_angularVelocity = actor.RotationalVelocity; } return m_angularVelocity; } set { if (Util.IsNanOrInfinity(value)) m_angularVelocity = Vector3.Zero; else m_angularVelocity = value; PhysicsActor actor = PhysActor; if ((actor != null) && actor.IsPhysical && ParentGroup.RootPart == this && VehicleType == (int)Vehicle.TYPE_NONE) { actor.RotationalVelocity = m_angularVelocity; } } } /// public Vector3 Acceleration { get { PhysicsActor actor = PhysActor; if (actor != null) { m_acceleration = actor.Acceleration; } return m_acceleration; } set { if (Util.IsNanOrInfinity(value)) m_acceleration = Vector3.Zero; else m_acceleration = value; } } public string Description { get; set; } /// /// Text color. /// public Color Color { get { return m_color; } set { m_color = value; } } public string Text { get { if (m_text.Length > 255) return m_text.Substring(0, 254); return m_text; } set { m_text = value; } } public string SitName { get { return m_sitName; } set { m_sitName = value; } } public string TouchName { get { return m_touchName; } set { m_touchName = value; } } public int LinkNum { get { return m_linkNum; } set { // if (ParentGroup != null) // { // m_log.DebugFormat( // "[SCENE OBJECT PART]: Setting linknum of {0}@{1} to {2} from {3}", // Name, AbsolutePosition, value, m_linkNum); // Util.PrintCallStack(); // } m_linkNum = value; } } public byte ClickAction { get { return m_clickAction; } set { m_clickAction = value; } } public PrimitiveBaseShape Shape { get { return m_shape; } set { m_shape = value; } } /// /// Change the scale of this part. /// public Vector3 Scale { get { return m_shape.Scale; } set { if (m_shape != null) { m_shape.Scale = value; PhysicsActor actor = PhysActor; if (actor != null) { if (ParentGroup.Scene != null) { if (ParentGroup.Scene.PhysicsScene != null) { actor.Size = m_shape.Scale; // if (Shape.SculptEntry) // CheckSculptAndLoad(); // else ParentGroup.Scene.PhysicsScene.AddPhysicsActorTaint(actor); } } } } TriggerScriptChangedEvent(Changed.SCALE); } } public UpdateRequired UpdateFlag { get; set; } public bool UpdatePhysRequired { get; set; } /// /// Used for media on a prim. /// /// Do not change this value directly - always do it through an IMoapModule. public string MediaUrl { get { return m_mediaUrl; } set { m_mediaUrl = value; if (ParentGroup != null) ParentGroup.HasGroupChanged = true; } } public bool CreateSelected { get { return m_createSelected; } set { // m_log.DebugFormat("[SOP]: Setting CreateSelected to {0} for {1} {2}", value, Name, UUID); m_createSelected = value; } } #endregion //--------------- #region Public Properties with only Get public Vector3 AbsolutePosition { get { return GroupPosition + (m_offsetPosition * ParentGroup.RootPart.RotationOffset); } } public SceneObjectGroup ParentGroup { get { return m_parentGroup; } private set { m_parentGroup = value; } } public scriptEvents ScriptEvents { get { return AggregateScriptEvents; } } public Quaternion SitTargetOrientation { get { return m_sitTargetOrientation; } set { m_sitTargetOrientation = value; // m_log.DebugFormat("[SCENE OBJECT PART]: Set sit target orientation {0} for {1} {2}", m_sitTargetOrientation, Name, LocalId); } } public Vector3 SitTargetPosition { get { return m_sitTargetPosition; } set { m_sitTargetPosition = value; // m_log.DebugFormat("[SCENE OBJECT PART]: Set sit target position to {0} for {1} {2}", m_sitTargetPosition, Name, LocalId); } } // This sort of sucks, but I'm adding these in to make some of // the mappings more consistant. public Vector3 SitTargetPositionLL { get { return m_sitTargetPosition; } set { m_sitTargetPosition = value; } } public Quaternion SitTargetOrientationLL { get { return m_sitTargetOrientation; } set { m_sitTargetOrientation = value; } } public bool Stopped { get { double threshold = 0.02; return (Math.Abs(Velocity.X) < threshold && Math.Abs(Velocity.Y) < threshold && Math.Abs(Velocity.Z) < threshold && Math.Abs(AngularVelocity.X) < threshold && Math.Abs(AngularVelocity.Y) < threshold && Math.Abs(AngularVelocity.Z) < threshold); } } /// /// The parent ID of this part. /// /// /// If this is a root part which is not attached to an avatar then the value will be 0. /// If this is a root part which is attached to an avatar then the value is the local id of that avatar. /// If this is a child part then the value is the local ID of the root part. /// public uint ParentID { get { return _parentID; } set { _parentID = value; } } public int CreationDate { get { return _creationDate; } set { _creationDate = value; } } public uint Category { get { return _category; } set { _category = value; } } public int SalePrice { get { return _salePrice; } set { _salePrice = value; } } public byte ObjectSaleType { get { return _objectSaleType; } set { _objectSaleType = value; } } public int OwnershipCost { get { return _ownershipCost; } set { _ownershipCost = value; } } public UUID GroupID { get { return _groupID; } set { _groupID = value; } } public UUID OwnerID { get { return _ownerID; } set { _ownerID = value; } } public UUID LastOwnerID { get { return _lastOwnerID; } set { _lastOwnerID = value; } } public uint BaseMask { get { return _baseMask; } set { _baseMask = value; } } public uint OwnerMask { get { return _ownerMask; } set { _ownerMask = value; } } public uint GroupMask { get { return _groupMask; } set { _groupMask = value; } } public uint EveryoneMask { get { return _everyoneMask; } set { _everyoneMask = value; } } public uint NextOwnerMask { get { return _nextOwnerMask; } set { _nextOwnerMask = value; } } /// /// Property flags. See OpenMetaverse.PrimFlags /// /// /// Example properties are PrimFlags.Phantom and PrimFlags.DieAtEdge /// public PrimFlags Flags { get { return _flags; } set { // m_log.DebugFormat("[SOP]: Setting flags for {0} {1} to {2}", UUID, Name, value); _flags = value; } } [XmlIgnore] public bool IsOccupied // KF If an av is sittingon this prim { get { return m_occupied; } set { m_occupied = value; } } /// /// ID of the avatar that is sat on us if we have a sit target. If there is no such avatar then is UUID.Zero /// public UUID SitTargetAvatar { get; set; } /// /// IDs of all avatars sat on this part. /// /// /// We need to track this so that we can stop sat upon prims from being attached. /// /// /// null if there are no sitting avatars. This is to save us create a hashset for every prim in a scene. /// private HashSet m_sittingAvatars; public virtual UUID RegionID { get { if (ParentGroup.Scene != null) return ParentGroup.Scene.RegionInfo.RegionID; else return UUID.Zero; } set {} // read only } private UUID _parentUUID = UUID.Zero; public UUID ParentUUID { get { if (ParentGroup != null) _parentUUID = ParentGroup.UUID; return _parentUUID; } set { _parentUUID = value; } } public string SitAnimation { get { return m_sitAnimation; } set { m_sitAnimation = value; } } public UUID invalidCollisionSoundUUID = new UUID("ffffffff-ffff-ffff-ffff-ffffffffffff"); // 0 for default collision sounds, -1 for script disabled sound 1 for script defined sound // runtime thing.. do not persist [XmlIgnore] public sbyte CollisionSoundType { get { return m_collisionSoundType; } set { m_collisionSoundType = value; if (value == -1) m_collisionSound = invalidCollisionSoundUUID; else if (value == 0) m_collisionSound = UUID.Zero; } } public UUID CollisionSound { get { return m_collisionSound; } set { m_collisionSound = value; if (value == invalidCollisionSoundUUID) m_collisionSoundType = -1; else if (value == UUID.Zero) m_collisionSoundType = 0; else m_collisionSoundType = 1; aggregateScriptEvents(); } } public float CollisionSoundVolume { get { return m_collisionSoundVolume; } set { m_collisionSoundVolume = value; } } public float Buoyancy { get { if (ParentGroup.RootPart == this) return m_buoyancy; return ParentGroup.RootPart.Buoyancy; } set { if (ParentGroup != null && ParentGroup.RootPart != null && ParentGroup.RootPart != this) { ParentGroup.RootPart.Buoyancy = value; return; } m_buoyancy = value; if (PhysActor != null) PhysActor.Buoyancy = value; } } public Vector3 Force { get { if (ParentGroup.RootPart == this) return m_force; return ParentGroup.RootPart.Force; } set { if (ParentGroup != null && ParentGroup.RootPart != null && ParentGroup.RootPart != this) { ParentGroup.RootPart.Force = value; return; } m_force = value; if (PhysActor != null) PhysActor.Force = value; } } public Vector3 Torque { get { if (ParentGroup.RootPart == this) return m_torque; return ParentGroup.RootPart.Torque; } set { if (ParentGroup != null && ParentGroup.RootPart != null && ParentGroup.RootPart != this) { ParentGroup.RootPart.Torque = value; return; } m_torque = value; if (PhysActor != null) PhysActor.Torque = value; } } public byte Material { get { return (byte)m_material; } set { if (value >= 0 && value <= (byte)SOPMaterialData.MaxMaterial) { bool update = false; if (m_material != (Material)value) { update = true; m_material = (Material)value; } if (m_friction != SOPMaterialData.friction(m_material)) { update = true; m_friction = SOPMaterialData.friction(m_material); } if (m_bounce != SOPMaterialData.bounce(m_material)) { update = true; m_bounce = SOPMaterialData.bounce(m_material); } if (update) { if (PhysActor != null) { PhysActor.SetMaterial((int)value); } if(ParentGroup != null) ParentGroup.HasGroupChanged = true; ScheduleFullUpdateIfNone(); UpdatePhysRequired = true; } } } } // not a propriety to move to methods place later private bool HasMesh() { if (Shape != null && (Shape.SculptType == (byte)SculptType.Mesh)) return true; return false; } // not a propriety to move to methods place later public byte DefaultPhysicsShapeType() { byte type; if (Shape != null && (Shape.SculptType == (byte)SculptType.Mesh)) type = (byte)PhysShapeType.convex; else type = (byte)PhysShapeType.prim; return type; } [XmlIgnore] public bool UsesComplexCost { get { byte pst = PhysicsShapeType; if(pst == (byte) PhysShapeType.none || pst == (byte) PhysShapeType.convex || HasMesh()) return true; return false; } } [XmlIgnore] public float PhysicsCost { get { if(PhysicsShapeType == (byte)PhysShapeType.none) return 0; float cost = 0.1f; if (PhysActor != null) cost = PhysActor.PhysicsCost; else cost = 0.1f; if ((Flags & PrimFlags.Physics) != 0) cost *= (1.0f + 0.01333f * Scale.LengthSquared()); // 0.01333 == 0.04/3 return cost; } } [XmlIgnore] public float StreamingCost { get { float cost; if (PhysActor != null) cost = PhysActor.StreamCost; else cost = 1.0f; return 1.0f; } } [XmlIgnore] public float SimulationCost { get { // ignoring scripts. Don't like considering them for this if((Flags & PrimFlags.Physics) != 0) return 1.0f; return 0.5f; } } public byte PhysicsShapeType { get { return m_physicsShapeType; } set { byte oldv = m_physicsShapeType; if (value >= 0 && value <= (byte)PhysShapeType.convex) { if (value == (byte)PhysShapeType.none && ParentGroup != null && ParentGroup.RootPart == this) m_physicsShapeType = DefaultPhysicsShapeType(); else m_physicsShapeType = value; } else m_physicsShapeType = DefaultPhysicsShapeType(); if (m_physicsShapeType != oldv && ParentGroup != null) { if (m_physicsShapeType == (byte)PhysShapeType.none) { if (PhysActor != null) { Velocity = new Vector3(0, 0, 0); Acceleration = new Vector3(0, 0, 0); if (ParentGroup.RootPart == this) AngularVelocity = new Vector3(0, 0, 0); ParentGroup.Scene.RemovePhysicalPrim(1); RemoveFromPhysics(); } } else if (PhysActor == null) { ApplyPhysics((uint)Flags, VolumeDetectActive, false); UpdatePhysicsSubscribedEvents(); } else { PhysActor.PhysicsShapeType = m_physicsShapeType; // if (Shape.SculptEntry) // CheckSculptAndLoad(); } if (ParentGroup != null) ParentGroup.HasGroupChanged = true; } if (m_physicsShapeType != value) { UpdatePhysRequired = true; } } } public float Density // in kg/m^3 { get { return m_density; } set { if (value >=1 && value <= 22587.0) { m_density = value; UpdatePhysRequired = true; } ScheduleFullUpdateIfNone(); if (ParentGroup != null) ParentGroup.HasGroupChanged = true; PhysicsActor pa = PhysActor; if (pa != null) pa.Density = Density; } } public float GravityModifier { get { return m_gravitymod; } set { if( value >= -1 && value <=28.0f) { m_gravitymod = value; UpdatePhysRequired = true; } ScheduleFullUpdateIfNone(); if (ParentGroup != null) ParentGroup.HasGroupChanged = true; PhysicsActor pa = PhysActor; if (pa != null) pa.GravModifier = GravityModifier; } } public float Friction { get { return m_friction; } set { if (value >= 0 && value <= 255.0f) { m_friction = value; UpdatePhysRequired = true; } ScheduleFullUpdateIfNone(); if (ParentGroup != null) ParentGroup.HasGroupChanged = true; PhysicsActor pa = PhysActor; if (pa != null) pa.Friction = Friction; } } public float Restitution { get { return m_bounce; } set { if (value >= 0 && value <= 1.0f) { m_bounce = value; UpdatePhysRequired = true; } ScheduleFullUpdateIfNone(); if (ParentGroup != null) ParentGroup.HasGroupChanged = true; PhysicsActor pa = PhysActor; if (pa != null) pa.Restitution = Restitution; } } #endregion Public Properties with only Get private uint ApplyMask(uint val, bool set, uint mask) { if (set) { return val |= mask; } else { return val &= ~mask; } } /// /// Clear all pending updates of parts to clients /// public void ClearUpdateSchedule() { UpdateFlag = UpdateRequired.NONE; } /// /// Send this part's properties (name, description, inventory serial, base mask, etc.) to a client /// /// public void SendPropertiesToClient(IClientAPI client) { client.SendObjectPropertiesReply(this); } // TODO: unused: // private void handleTimerAccounting(uint localID, double interval) // { // if (localID == LocalId) // { // float sec = (float)interval; // if (m_parentGroup != null) // { // if (sec == 0) // { // if (m_parentGroup.scriptScore + 0.001f >= float.MaxValue - 0.001) // m_parentGroup.scriptScore = 0; // // m_parentGroup.scriptScore += 0.001f; // return; // } // // if (m_parentGroup.scriptScore + (0.001f / sec) >= float.MaxValue - (0.001f / sec)) // m_parentGroup.scriptScore = 0; // m_parentGroup.scriptScore += (0.001f / sec); // } // } // } #region Public Methods public void ResetExpire() { Expires = DateTime.Now + new TimeSpan(600000000); } public void AddFlag(PrimFlags flag) { // PrimFlags prevflag = Flags; if ((Flags & flag) == 0) { //m_log.Debug("Adding flag: " + ((PrimFlags) flag).ToString()); Flags |= flag; if (flag == PrimFlags.TemporaryOnRez) ResetExpire(); } // m_log.Debug("Aprev: " + prevflag.ToString() + " curr: " + Flags.ToString()); } public void AddNewParticleSystem(Primitive.ParticleSystem pSystem) { m_particleSystem = pSystem.GetBytes(); } public void RemoveParticleSystem() { m_particleSystem = new byte[0]; } public void AddTextureAnimation(Primitive.TextureAnimation pTexAnim) { byte[] data; if (pTexAnim.Flags == Primitive.TextureAnimMode.ANIM_OFF) { data = Utils.EmptyBytes; } else { data = new byte[16]; int pos = 0; // The flags don't like conversion from uint to byte, so we have to do // it the crappy way. See the above function :( data[pos] = ConvertScriptUintToByte((uint)pTexAnim.Flags); pos++; data[pos] = (byte)pTexAnim.Face; pos++; data[pos] = (byte)pTexAnim.SizeX; pos++; data[pos] = (byte)pTexAnim.SizeY; pos++; Utils.FloatToBytes(pTexAnim.Start).CopyTo(data, pos); Utils.FloatToBytes(pTexAnim.Length).CopyTo(data, pos + 4); Utils.FloatToBytes(pTexAnim.Rate).CopyTo(data, pos + 8); } m_TextureAnimation = data; } public void AdjustSoundGain(double volume) { if (volume > 1) volume = 1; if (volume < 0) volume = 0; ParentGroup.Scene.ForEachRootClient(delegate(IClientAPI client) { client.SendAttachedSoundGainChange(UUID, (float)volume); }); } /// /// hook to the physics scene to apply impulse /// This is sent up to the group, which then finds the root prim /// and applies the force on the root prim of the group /// /// Vector force /// true for the local frame, false for the global frame public void ApplyImpulse(Vector3 impulsei, bool localGlobalTF) { Vector3 impulse = impulsei; if (localGlobalTF) { Quaternion grot = GetWorldRotation(); Quaternion AXgrot = grot; Vector3 AXimpulsei = impulsei; Vector3 newimpulse = AXimpulsei * AXgrot; impulse = newimpulse; } if (ParentGroup != null) { ParentGroup.applyImpulse(impulse); } } // SetVelocity for LSL llSetVelocity.. may need revision if having other uses in future public void SetVelocity(Vector3 pVel, bool localGlobalTF) { if (ParentGroup == null || ParentGroup.IsDeleted) return; if (ParentGroup.IsAttachment) return; // don't work on attachments (for now ??) SceneObjectPart root = ParentGroup.RootPart; if (root.VehicleType != (int)Vehicle.TYPE_NONE) // don't mess with vehicles return; PhysicsActor pa = root.PhysActor; if (pa == null || !pa.IsPhysical) return; if (localGlobalTF) { pVel = pVel * GetWorldRotation(); } ParentGroup.Velocity = pVel; } // SetAngularVelocity for LSL llSetAngularVelocity.. may need revision if having other uses in future public void SetAngularVelocity(Vector3 pAngVel, bool localGlobalTF) { if (ParentGroup == null || ParentGroup.IsDeleted) return; if (ParentGroup.IsAttachment) return; // don't work on attachments (for now ??) SceneObjectPart root = ParentGroup.RootPart; if (root.VehicleType != (int)Vehicle.TYPE_NONE) // don't mess with vehicles return; PhysicsActor pa = root.PhysActor; if (pa == null || !pa.IsPhysical) return; if (localGlobalTF) { pAngVel = pAngVel * GetWorldRotation(); } root.AngularVelocity = pAngVel; } /// /// hook to the physics scene to apply angular impulse /// This is sent up to the group, which then finds the root prim /// and applies the force on the root prim of the group /// /// Vector force /// true for the local frame, false for the global frame public void ApplyAngularImpulse(Vector3 impulsei, bool localGlobalTF) { Vector3 impulse = impulsei; if (localGlobalTF) { Quaternion grot = GetWorldRotation(); Quaternion AXgrot = grot; Vector3 AXimpulsei = impulsei; Vector3 newimpulse = AXimpulsei * AXgrot; impulse = newimpulse; } ParentGroup.ApplyAngularImpulse(impulse); } /// /// hook to the physics scene to apply angular impulse /// This is sent up to the group, which then finds the root prim /// and applies the force on the root prim of the group /// /// Vector force /// true for the local frame, false for the global frame // this is actualy Set Torque.. keeping naming so not to edit lslapi also public void SetAngularImpulse(Vector3 torquei, bool localGlobalTF) { Vector3 torque = torquei; if (localGlobalTF) { /* Quaternion grot = GetWorldRotation(); Quaternion AXgrot = grot; Vector3 AXimpulsei = impulsei; Vector3 newimpulse = AXimpulsei * AXgrot; */ torque *= GetWorldRotation(); } Torque = torque; } /// /// Apply physics to this part. /// /// /// /// public void ApplyPhysics(uint _ObjectFlags, bool _VolumeDetectActive, bool building) { VolumeDetectActive = _VolumeDetectActive; if (!ParentGroup.Scene.CollidablePrims) return; if (PhysicsShapeType == (byte)PhysShapeType.none) return; bool isPhysical = (_ObjectFlags & (uint) PrimFlags.Physics) != 0; bool isPhantom = (_ObjectFlags & (uint)PrimFlags.Phantom) != 0; if (_VolumeDetectActive) isPhantom = true; if (IsJoint()) { DoPhysicsPropertyUpdate(isPhysical, true); } else { if ((!isPhantom || isPhysical || _VolumeDetectActive) && !ParentGroup.IsAttachment && !(Shape.PathCurve == (byte)Extrusion.Flexible)) { AddToPhysics(isPhysical, isPhantom, building, isPhysical); UpdatePhysicsSubscribedEvents(); // not sure if appliable here } else PhysActor = null; // just to be sure } } public byte ConvertScriptUintToByte(uint indata) { byte outdata = (byte)TextureAnimFlags.NONE; if ((indata & 1) != 0) outdata |= (byte)TextureAnimFlags.ANIM_ON; if ((indata & 2) != 0) outdata |= (byte)TextureAnimFlags.LOOP; if ((indata & 4) != 0) outdata |= (byte)TextureAnimFlags.REVERSE; if ((indata & 8) != 0) outdata |= (byte)TextureAnimFlags.PING_PONG; if ((indata & 16) != 0) outdata |= (byte)TextureAnimFlags.SMOOTH; if ((indata & 32) != 0) outdata |= (byte)TextureAnimFlags.ROTATE; if ((indata & 64) != 0) outdata |= (byte)TextureAnimFlags.SCALE; return outdata; } /// /// Duplicates this part. /// /// /// /// /// /// True if the duplicate will immediately be in the scene, false otherwise /// public SceneObjectPart Copy(uint plocalID, UUID AgentID, UUID GroupID, int linkNum, bool userExposed) { // FIXME: This is dangerous since it's easy to forget to reset some references when necessary and end up // with bugs that only occur in some circumstances (e.g. crossing between regions on the same simulator // but not between regions on different simulators). Really, all copying should be done explicitly. SceneObjectPart dupe = (SceneObjectPart)MemberwiseClone(); dupe.m_shape = m_shape.Copy(); dupe.m_regionHandle = m_regionHandle; if (userExposed) dupe.UUID = UUID.Random(); dupe.PhysActor = null; dupe.OwnerID = AgentID; dupe.GroupID = GroupID; dupe.GroupPosition = GroupPosition; dupe.OffsetPosition = OffsetPosition; dupe.RotationOffset = RotationOffset; dupe.Velocity = Velocity; dupe.Acceleration = Acceleration; dupe.AngularVelocity = AngularVelocity; dupe.Flags = Flags; dupe.OwnershipCost = OwnershipCost; dupe.ObjectSaleType = ObjectSaleType; dupe.SalePrice = SalePrice; dupe.Category = Category; dupe.m_rezzed = m_rezzed; dupe.m_UndoRedo = null; dupe.m_isSelected = false; dupe.IgnoreUndoUpdate = false; dupe.Undoing = false; dupe.m_inventory = new SceneObjectPartInventory(dupe); dupe.m_inventory.Items = (TaskInventoryDictionary)m_inventory.Items.Clone(); if (userExposed) { dupe.ResetIDs(linkNum); dupe.m_inventory.HasInventoryChanged = true; } else { dupe.m_inventory.HasInventoryChanged = m_inventory.HasInventoryChanged; } // Move afterwards ResetIDs as it clears the localID dupe.LocalId = plocalID; // This may be wrong... it might have to be applied in SceneObjectGroup to the object that's being duplicated. dupe.LastOwnerID = OwnerID; byte[] extraP = new byte[Shape.ExtraParams.Length]; Array.Copy(Shape.ExtraParams, extraP, extraP.Length); dupe.Shape.ExtraParams = extraP; dupe.m_sittingAvatars = new HashSet(); // safeguard actual copy is done in sog.copy dupe.KeyframeMotion = null; dupe.PayPrice = (int[])PayPrice.Clone(); dupe.DynAttrs.CopyFrom(DynAttrs); if (userExposed) { /* if (dupe.m_shape.SculptEntry && dupe.m_shape.SculptTexture != UUID.Zero) { ParentGroup.Scene.AssetService.Get( dupe.m_shape.SculptTexture.ToString(), dupe, dupe.AssetReceived); } */ bool UsePhysics = ((dupe.Flags & PrimFlags.Physics) != 0); dupe.DoPhysicsPropertyUpdate(UsePhysics, true); // dupe.UpdatePhysicsSubscribedEvents(); // not sure... } if (dupe.PhysActor != null) dupe.PhysActor.LocalID = plocalID; ParentGroup.Scene.EventManager.TriggerOnSceneObjectPartCopy(dupe, this, userExposed); // m_log.DebugFormat("[SCENE OBJECT PART]: Clone of {0} {1} finished", Name, UUID); return dupe; } /// /// Called back by asynchronous asset fetch. /// /// ID of asset received /// Register /// /* protected void AssetReceived(string id, Object sender, AssetBase asset) { if (asset != null) SculptTextureCallback(asset); // else // m_log.WarnFormat( // "[SCENE OBJECT PART]: Part {0} {1} requested mesh/sculpt data for asset id {2} from asset service but received no data", // Name, UUID, id); } */ /// /// Do a physics property update for a NINJA joint. /// /// /// protected void DoPhysicsPropertyUpdateForNinjaJoint(bool UsePhysics, bool isNew) { if (UsePhysics) { // by turning a joint proxy object physical, we cause creation of a joint in the ODE scene. // note that, as a special case, joints have no bodies or geoms in the physics scene, even though they are physical. PhysicsJointType jointType; if (IsHingeJoint()) { jointType = PhysicsJointType.Hinge; } else if (IsBallJoint()) { jointType = PhysicsJointType.Ball; } else { jointType = PhysicsJointType.Ball; } List bodyNames = new List(); string RawParams = Description; string[] jointParams = RawParams.Split(" ".ToCharArray(), System.StringSplitOptions.RemoveEmptyEntries); string trackedBodyName = null; if (jointParams.Length >= 2) { for (int iBodyName = 0; iBodyName < 2; iBodyName++) { string bodyName = jointParams[iBodyName]; bodyNames.Add(bodyName); if (bodyName != "NULL") { if (trackedBodyName == null) { trackedBodyName = bodyName; } } } } SceneObjectPart trackedBody = ParentGroup.Scene.GetSceneObjectPart(trackedBodyName); // FIXME: causes a sequential lookup Quaternion localRotation = Quaternion.Identity; if (trackedBody != null) { localRotation = Quaternion.Inverse(trackedBody.RotationOffset) * this.RotationOffset; } else { // error, output it below } PhysicsJoint joint; joint = ParentGroup.Scene.PhysicsScene.RequestJointCreation(Name, jointType, AbsolutePosition, this.RotationOffset, Description, bodyNames, trackedBodyName, localRotation); if (trackedBody == null) { ParentGroup.Scene.jointErrorMessage(joint, "warning: tracked body name not found! joint location will not be updated properly. joint: " + Name); } } else { if (isNew) { // if the joint proxy is new, and it is not physical, do nothing. There is no joint in ODE to // delete, and if we try to delete it, due to asynchronous processing, the deletion request // will get processed later at an indeterminate time, which could cancel a later-arriving // joint creation request. } else { // here we turn off the joint object, so remove the joint from the physics scene ParentGroup.Scene.PhysicsScene.RequestJointDeletion(Name); // FIXME: what if the name changed? // make sure client isn't interpolating the joint proxy object Velocity = Vector3.Zero; AngularVelocity = Vector3.Zero; Acceleration = Vector3.Zero; } } } /// /// Do a physics propery update for this part. /// now also updates phantom and volume detector /// /// /// public void DoPhysicsPropertyUpdate(bool UsePhysics, bool isNew) { if (ParentGroup.Scene == null) return; if (!ParentGroup.Scene.PhysicalPrims && UsePhysics) return; if (IsJoint()) { DoPhysicsPropertyUpdateForNinjaJoint(UsePhysics, isNew); } else { PhysicsActor pa = PhysActor; if (pa != null) { if (UsePhysics != pa.IsPhysical || isNew) { if (pa.IsPhysical) // implies UsePhysics==false for this block { if (!isNew) // implies UsePhysics==false for this block { ParentGroup.Scene.RemovePhysicalPrim(1); Velocity = new Vector3(0, 0, 0); Acceleration = new Vector3(0, 0, 0); if (ParentGroup.RootPart == this) AngularVelocity = new Vector3(0, 0, 0); if (pa.Phantom && !VolumeDetectActive) { RemoveFromPhysics(); return; } pa.IsPhysical = UsePhysics; pa.OnRequestTerseUpdate -= PhysicsRequestingTerseUpdate; pa.OnOutOfBounds -= PhysicsOutOfBounds; pa.delink(); if (ParentGroup.Scene.PhysicsScene.SupportsNINJAJoints) { // destroy all joints connected to this now deactivated body ParentGroup.Scene.PhysicsScene.RemoveAllJointsConnectedToActorThreadLocked(pa); } } } if (pa.IsPhysical != UsePhysics) pa.IsPhysical = UsePhysics; if (UsePhysics) { if (ParentGroup.RootPart.KeyframeMotion != null) ParentGroup.RootPart.KeyframeMotion.Stop(); ParentGroup.RootPart.KeyframeMotion = null; ParentGroup.Scene.AddPhysicalPrim(1); PhysActor.OnRequestTerseUpdate += PhysicsRequestingTerseUpdate; PhysActor.OnOutOfBounds += PhysicsOutOfBounds; if (ParentID != 0 && ParentID != LocalId) { PhysicsActor parentPa = ParentGroup.RootPart.PhysActor; if (parentPa != null) { pa.link(parentPa); } } } } bool phan = ((Flags & PrimFlags.Phantom) != 0); if (pa.Phantom != phan) pa.Phantom = phan; // some engines dont' have this check still // if (VolumeDetectActive != pa.IsVolumeDtc) { if (VolumeDetectActive) pa.SetVolumeDetect(1); else pa.SetVolumeDetect(0); } // If this part is a sculpt then delay the physics update until we've asynchronously loaded the // mesh data. // if (Shape.SculptEntry) // CheckSculptAndLoad(); // else ParentGroup.Scene.PhysicsScene.AddPhysicsActorTaint(pa); } } } /// /// Restore this part from the serialized xml representation. /// /// /// public static SceneObjectPart FromXml(XmlReader xmlReader) { SceneObjectPart part = SceneObjectSerializer.Xml2ToSOP(xmlReader); // for tempOnRez objects, we have to fix the Expire date. if ((part.Flags & PrimFlags.TemporaryOnRez) != 0) part.ResetExpire(); return part; } public bool GetDieAtEdge() { if (ParentGroup.IsDeleted) return false; return ParentGroup.RootPart.DIE_AT_EDGE; } public bool GetReturnAtEdge() { if (ParentGroup.IsDeleted) return false; return ParentGroup.RootPart.RETURN_AT_EDGE; } public void SetReturnAtEdge(bool p) { if (ParentGroup.IsDeleted) return; ParentGroup.RootPart.RETURN_AT_EDGE = p; } public void SetStatusSandbox(bool p) { if (ParentGroup.IsDeleted) return; StatusSandboxPos = ParentGroup.RootPart.AbsolutePosition; ParentGroup.RootPart.StatusSandbox = p; } public bool GetStatusSandbox() { if (ParentGroup.IsDeleted) return false; return ParentGroup.RootPart.StatusSandbox; } public int GetAxisRotation(int axis) { //Cannot use ScriptBaseClass constants as no referance to it currently. if (axis == (int)SceneObjectGroup.axisSelect.STATUS_ROTATE_X) return STATUS_ROTATE_X; if (axis == (int)SceneObjectGroup.axisSelect.STATUS_ROTATE_Y) return STATUS_ROTATE_Y; if (axis == (int)SceneObjectGroup.axisSelect.STATUS_ROTATE_Z) return STATUS_ROTATE_Z; return 0; } public double GetDistanceTo(Vector3 a, Vector3 b) { float dx = a.X - b.X; float dy = a.Y - b.Y; float dz = a.Z - b.Z; return Math.Sqrt(dx * dx + dy * dy + dz * dz); } public uint GetEffectiveObjectFlags() { // Commenting this section of code out since it doesn't actually do anything, as enums are handled by // value rather than reference // PrimFlags f = _flags; // if (m_parentGroup == null || m_parentGroup.RootPart == this) // f &= ~(PrimFlags.Touch | PrimFlags.Money); return (uint)Flags | (uint)LocalFlags; } public Vector3 GetGeometricCenter() { // this is not real geometric center but a average of positions relative to root prim acording to // http://wiki.secondlife.com/wiki/llGetGeometricCenter // ignoring tortured prims details since sl also seems to ignore // so no real use in doing it on physics if (ParentGroup.IsDeleted) return new Vector3(0, 0, 0); return ParentGroup.GetGeometricCenter(); } public float GetMass() { PhysicsActor pa = PhysActor; if (pa != null) return pa.Mass; else return 0; } public Vector3 GetCenterOfMass() { if (ParentGroup.RootPart == this) { if (ParentGroup.IsDeleted) return AbsolutePosition; return ParentGroup.GetCenterOfMass(); } PhysicsActor pa = PhysActor; if (pa != null) { Vector3 tmp = pa.CenterOfMass; return tmp; } else return AbsolutePosition; } public Vector3 GetPartCenterOfMass() { PhysicsActor pa = PhysActor; if (pa != null) { Vector3 tmp = pa.CenterOfMass; return tmp; } else return AbsolutePosition; } public Vector3 GetForce() { return Force; } /// /// Method for a prim to get it's world position from the group. /// /// /// Remember, the Group Position simply gives the position of the group itself /// /// A Linked Child Prim objects position in world public Vector3 GetWorldPosition() { Vector3 ret; if (_parentID == 0) // if a root SOP, my position is what it is ret = GroupPosition; else { // If a child SOP, my position is relative to the root SOP so take // my info and add the root's position and rotation to // get my world position. Quaternion parentRot = ParentGroup.RootPart.RotationOffset; Vector3 translationOffsetPosition = OffsetPosition * parentRot; ret = ParentGroup.AbsolutePosition + translationOffsetPosition; } return ret; } /// /// Gets the rotation of this prim offset by the group rotation /// /// public Quaternion GetWorldRotation() { Quaternion newRot; if (this.LinkNum == 0 || this.LinkNum == 1) { newRot = RotationOffset; } else { // A child SOP's rotation is relative to the root SOP's rotation. // Combine them to get my absolute rotation. Quaternion parentRot = ParentGroup.RootPart.RotationOffset; Quaternion oldRot = RotationOffset; newRot = parentRot * oldRot; } return newRot; } public void MoveToTarget(Vector3 target, float tau) { if (tau > 0) { ParentGroup.MoveToTarget(target, tau); } else { StopMoveToTarget(); } } /// /// Uses a PID to attempt to clamp the object on the Z axis at the given height over tau seconds. /// /// Height to hover. Height of zero disables hover. /// Determines what the height is relative to /// Number of seconds over which to reach target public void SetHoverHeight(float height, PIDHoverType hoverType, float tau) { ParentGroup.SetHoverHeight(height, hoverType, tau); } public void StopHover() { ParentGroup.SetHoverHeight(0f, PIDHoverType.Ground, 0f); } public virtual void OnGrab(Vector3 offsetPos, IClientAPI remoteClient) { } public bool CollisionFilteredOut(UUID objectID, string objectName) { if(CollisionFilter.Count == 0) return false; if (CollisionFilter.ContainsValue(objectID.ToString()) || CollisionFilter.ContainsValue(objectID.ToString() + objectName) || CollisionFilter.ContainsValue(UUID.Zero.ToString() + objectName)) { if (CollisionFilter.ContainsKey(1)) return false; return true; } if (CollisionFilter.ContainsKey(1)) return true; return false; } private DetectedObject CreateDetObject(SceneObjectPart obj) { DetectedObject detobj = new DetectedObject(); detobj.keyUUID = obj.UUID; detobj.nameStr = obj.Name; detobj.ownerUUID = obj.OwnerID; detobj.posVector = obj.AbsolutePosition; detobj.rotQuat = obj.GetWorldRotation(); detobj.velVector = obj.Velocity; detobj.colliderType = 0; detobj.groupUUID = obj.GroupID; detobj.linkNumber = LinkNum; // pass my link number return detobj; } private DetectedObject CreateDetObject(ScenePresence av) { DetectedObject detobj = new DetectedObject(); detobj.keyUUID = av.UUID; detobj.nameStr = av.ControllingClient.Name; detobj.ownerUUID = av.UUID; detobj.posVector = av.AbsolutePosition; detobj.rotQuat = av.Rotation; detobj.velVector = av.Velocity; detobj.colliderType = 0; detobj.groupUUID = av.ControllingClient.ActiveGroupId; detobj.linkNumber = LinkNum; // pass my link number return detobj; } private DetectedObject CreateDetObjectForGround() { DetectedObject detobj = new DetectedObject(); detobj.keyUUID = UUID.Zero; detobj.nameStr = ""; detobj.ownerUUID = UUID.Zero; detobj.posVector = ParentGroup.RootPart.AbsolutePosition; detobj.rotQuat = Quaternion.Identity; detobj.velVector = Vector3.Zero; detobj.colliderType = 0; detobj.groupUUID = UUID.Zero; detobj.linkNumber = LinkNum; // pass my link number not sure needed.. but no harm return detobj; } private ColliderArgs CreateColliderArgs(SceneObjectPart dest, List colliders) { ColliderArgs colliderArgs = new ColliderArgs(); List colliding = new List(); foreach (uint localId in colliders) { if (localId == 0) continue; SceneObjectPart obj = ParentGroup.Scene.GetSceneObjectPart(localId); if (obj != null) { if (!dest.CollisionFilteredOut(obj.UUID, obj.Name)) colliding.Add(CreateDetObject(obj)); } else { ScenePresence av = ParentGroup.Scene.GetScenePresence(localId); if (av != null && (!av.IsChildAgent)) { if (!dest.CollisionFilteredOut(av.UUID, av.Name)) colliding.Add(CreateDetObject(av)); } } } colliderArgs.Colliders = colliding; return colliderArgs; } private delegate void ScriptCollidingNotification(uint localID, ColliderArgs message); private void SendCollisionEvent(scriptEvents ev, List colliders, ScriptCollidingNotification notify) { bool sendToRoot = false; ColliderArgs CollidingMessage; if (colliders.Count > 0) { if ((ScriptEvents & ev) != 0) { CollidingMessage = CreateColliderArgs(this, colliders); if (CollidingMessage.Colliders.Count > 0) notify(LocalId, CollidingMessage); if (PassCollisions) sendToRoot = true; } else { if ((ParentGroup.RootPart.ScriptEvents & ev) != 0) sendToRoot = true; } if (sendToRoot && ParentGroup.RootPart != this) { CollidingMessage = CreateColliderArgs(ParentGroup.RootPart, colliders); if (CollidingMessage.Colliders.Count > 0) notify(ParentGroup.RootPart.LocalId, CollidingMessage); } } } private void SendLandCollisionEvent(scriptEvents ev, ScriptCollidingNotification notify) { bool sendToRoot = true; ColliderArgs LandCollidingMessage = new ColliderArgs(); List colliding = new List(); colliding.Add(CreateDetObjectForGround()); LandCollidingMessage.Colliders = colliding; if (Inventory.ContainsScripts()) { if (!PassCollisions) sendToRoot = false; } if ((ScriptEvents & ev) != 0) notify(LocalId, LandCollidingMessage); if ((ParentGroup.RootPart.ScriptEvents & ev) != 0 && sendToRoot) { notify(ParentGroup.RootPart.LocalId, LandCollidingMessage); } } public void PhysicsCollision(EventArgs e) { if (ParentGroup.Scene == null || ParentGroup.IsDeleted) return; // single threaded here CollisionEventUpdate a = (CollisionEventUpdate)e; Dictionary collissionswith = a.m_objCollisionList; List thisHitColliders = new List(); List endedColliders = new List(); List startedColliders = new List(); if (collissionswith.Count == 0) { if (m_lastColliders.Count == 0) return; // nothing to do foreach (uint localID in m_lastColliders) { endedColliders.Add(localID); } m_lastColliders.Clear(); } else { List soundinfolist = new List(); // calculate things that started colliding this time // and build up list of colliders this time if (!VolumeDetectActive && CollisionSoundType >= 0) { CollisionForSoundInfo soundinfo; ContactPoint curcontact; foreach (uint id in collissionswith.Keys) { thisHitColliders.Add(id); if (!m_lastColliders.Contains(id)) { startedColliders.Add(id); curcontact = collissionswith[id]; if (Math.Abs(curcontact.RelativeSpeed) > 0.2) { soundinfo = new CollisionForSoundInfo(); soundinfo.colliderID = id; soundinfo.position = curcontact.Position; soundinfo.relativeVel = curcontact.RelativeSpeed; soundinfolist.Add(soundinfo); } } } } else { foreach (uint id in collissionswith.Keys) { thisHitColliders.Add(id); if (!m_lastColliders.Contains(id)) startedColliders.Add(id); } } // calculate things that ended colliding foreach (uint localID in m_lastColliders) { if (!thisHitColliders.Contains(localID)) endedColliders.Add(localID); } //add the items that started colliding this time to the last colliders list. foreach (uint localID in startedColliders) m_lastColliders.Add(localID); // remove things that ended colliding from the last colliders list foreach (uint localID in endedColliders) m_lastColliders.Remove(localID); // play sounds. if (soundinfolist.Count > 0) CollisionSounds.PartCollisionSound(this, soundinfolist); } SendCollisionEvent(scriptEvents.collision_start, startedColliders, ParentGroup.Scene.EventManager.TriggerScriptCollidingStart); if (!VolumeDetectActive) SendCollisionEvent(scriptEvents.collision , m_lastColliders , ParentGroup.Scene.EventManager.TriggerScriptColliding); SendCollisionEvent(scriptEvents.collision_end , endedColliders , ParentGroup.Scene.EventManager.TriggerScriptCollidingEnd); if (startedColliders.Contains(0)) SendLandCollisionEvent(scriptEvents.land_collision_start, ParentGroup.Scene.EventManager.TriggerScriptLandCollidingStart); if (m_lastColliders.Contains(0)) SendLandCollisionEvent(scriptEvents.land_collision, ParentGroup.Scene.EventManager.TriggerScriptLandColliding); if (endedColliders.Contains(0)) SendLandCollisionEvent(scriptEvents.land_collision_end, ParentGroup.Scene.EventManager.TriggerScriptLandCollidingEnd); } // The Collision sounds code calls this public void SendCollisionSound(UUID soundID, double volume, Vector3 position) { if (soundID == UUID.Zero) return; ISoundModule soundModule = ParentGroup.Scene.RequestModuleInterface(); if (soundModule == null) return; if (volume > 1) volume = 1; if (volume < 0) volume = 0; int now = Util.EnvironmentTickCount(); if(Util.EnvironmentTickCountSubtract(now,LastColSoundSentTime) <200) return; LastColSoundSentTime = now; UUID ownerID = OwnerID; UUID objectID = ParentGroup.RootPart.UUID; UUID parentID = ParentGroup.UUID; ulong regionHandle = ParentGroup.Scene.RegionInfo.RegionHandle; soundModule.TriggerSound(soundID, ownerID, objectID, parentID, volume, position, regionHandle, 0 ); } public void PhysicsOutOfBounds(Vector3 pos) { // Note: This is only being called on the root prim at this time. m_log.ErrorFormat( "[SCENE OBJECT PART]: Physical object {0}, localID {1} went out of bounds at {2} in {3}. Stopping at {4} and making non-physical.", Name, LocalId, pos, ParentGroup.Scene.Name, AbsolutePosition); RemFlag(PrimFlags.Physics); DoPhysicsPropertyUpdate(false, true); } public void PhysicsRequestingTerseUpdate() { PhysicsActor pa = PhysActor; if (pa != null) { Vector3 newpos = pa.Position; if (!ParentGroup.Scene.PositionIsInCurrentRegion(newpos)) { // Setting position outside current region will start region crossing ParentGroup.AbsolutePosition = newpos; return; } //ParentGroup.RootPart.m_groupPosition = newpos; } /* ubit: there are no flexible links if (pa != null && ParentID != 0 && ParentGroup != null) { // Special case where a child object is requesting property updates. // This happens when linksets are modified to use flexible links rather than // the default links. // The simulator code presumes that child parts are only modified by scripts // so the logic for changing position/rotation/etc does not take into // account the physical object actually moving. // This code updates the offset position and rotation of the child and then // lets the update code push the update to the viewer. // Since physics engines do not normally generate this event for linkset children, // this code will not be active unless you have a specially configured // physics engine. Quaternion invRootRotation = Quaternion.Normalize(Quaternion.Inverse(ParentGroup.RootPart.RotationOffset)); m_offsetPosition = pa.Position - m_groupPosition; RotationOffset = pa.Orientation * invRootRotation; // m_log.DebugFormat("{0} PhysicsRequestingTerseUpdate child: pos={1}, rot={2}, offPos={3}, offRot={4}", // "[SCENE OBJECT PART]", pa.Position, pa.Orientation, m_offsetPosition, RotationOffset); } */ ScheduleTerseUpdate(); } public void RemFlag(PrimFlags flag) { // PrimFlags prevflag = Flags; if ((Flags & flag) != 0) { //m_log.Debug("Removing flag: " + ((PrimFlags)flag).ToString()); Flags &= ~flag; } //m_log.Debug("prev: " + prevflag.ToString() + " curr: " + Flags.ToString()); //ScheduleFullUpdate(); } public void RemoveScriptEvents(UUID scriptid) { lock (m_scriptEvents) { if (m_scriptEvents.ContainsKey(scriptid)) { scriptEvents oldparts = scriptEvents.None; oldparts = (scriptEvents) m_scriptEvents[scriptid]; // remove values from aggregated script events AggregateScriptEvents &= ~oldparts; m_scriptEvents.Remove(scriptid); aggregateScriptEvents(); } } } /// /// Reset UUIDs for this part. This involves generate this part's own UUID and /// generating new UUIDs for all the items in the inventory. /// /// Link number for the part public void ResetIDs(int linkNum) { UUID = UUID.Random(); LinkNum = linkNum; LocalId = 0; Inventory.ResetInventoryIDs(); } /// /// Set the scale of this part. /// /// /// Unlike the scale property, this checks the new size against scene limits and schedules a full property /// update to viewers. /// /// public void Resize(Vector3 scale) { PhysicsActor pa = PhysActor; if (ParentGroup.Scene != null) { scale.X = Math.Max(ParentGroup.Scene.m_minNonphys, Math.Min(ParentGroup.Scene.m_maxNonphys, scale.X)); scale.Y = Math.Max(ParentGroup.Scene.m_minNonphys, Math.Min(ParentGroup.Scene.m_maxNonphys, scale.Y)); scale.Z = Math.Max(ParentGroup.Scene.m_minNonphys, Math.Min(ParentGroup.Scene.m_maxNonphys, scale.Z)); if (pa != null && pa.IsPhysical) { scale.X = Math.Max(ParentGroup.Scene.m_minPhys, Math.Min(ParentGroup.Scene.m_maxPhys, scale.X)); scale.Y = Math.Max(ParentGroup.Scene.m_minPhys, Math.Min(ParentGroup.Scene.m_maxPhys, scale.Y)); scale.Z = Math.Max(ParentGroup.Scene.m_minPhys, Math.Min(ParentGroup.Scene.m_maxPhys, scale.Z)); } } // m_log.DebugFormat("[SCENE OBJECT PART]: Resizing {0} {1} to {2}", Name, LocalId, scale); Scale = scale; ParentGroup.HasGroupChanged = true; ScheduleFullUpdate(); } public void RotLookAt(Quaternion target, float strength, float damping) { if (ParentGroup.IsAttachment) { /* ScenePresence avatar = m_scene.GetScenePresence(rootpart.AttachedAvatar); if (avatar != null) { Rotate the Av? } */ } else { APIDDamp = damping; APIDStrength = strength; APIDTarget = target; if (APIDStrength <= 0) { m_log.WarnFormat("[SceneObjectPart] Invalid rotation strength {0}",APIDStrength); return; } APIDActive = true; } // Necessary to get the lookat deltas applied ParentGroup.QueueForUpdateCheck(); } public void StartLookAt(Quaternion target, float strength, float damping) { RotLookAt(target,strength,damping); } public void StopLookAt() { APIDActive = false; } public void ScheduleFullUpdateIfNone() { if (ParentGroup == null) return; // ??? ParentGroup.HasGroupChanged = true; if (UpdateFlag != UpdateRequired.FULL) ScheduleFullUpdate(); } /// /// Schedules this prim for a full update /// public void ScheduleFullUpdate() { // m_log.DebugFormat("[SCENE OBJECT PART]: Scheduling full update for {0} {1}", Name, LocalId); if (ParentGroup == null) return; ParentGroup.QueueForUpdateCheck(); int timeNow = Util.UnixTimeSinceEpoch(); // If multiple updates are scheduled on the same second, we still need to perform all of them // So we'll force the issue by bumping up the timestamp so that later processing sees these need // to be performed. if (timeNow <= TimeStampFull) { TimeStampFull += 1; } else { TimeStampFull = (uint)timeNow; } UpdateFlag = UpdateRequired.FULL; // m_log.DebugFormat( // "[SCENE OBJECT PART]: Scheduling full update for {0}, {1} at {2}", // UUID, Name, TimeStampFull); if (ParentGroup.Scene != null) ParentGroup.Scene.EventManager.TriggerSceneObjectPartUpdated(this, true); } /// /// Schedule a terse update for this prim. Terse updates only send position, /// rotation, velocity and rotational velocity information. /// public void ScheduleTerseUpdate() { if (ParentGroup == null) return; // This was pulled from SceneViewer. Attachments always receive full updates. // This is needed because otherwise if only the root prim changes position, then // it looks as if the entire object has moved (including the other prims). if (ParentGroup.IsAttachment) { ScheduleFullUpdate(); return; } if (UpdateFlag == UpdateRequired.NONE) { ParentGroup.HasGroupChanged = true; ParentGroup.QueueForUpdateCheck(); TimeStampTerse = (uint) Util.UnixTimeSinceEpoch(); UpdateFlag = UpdateRequired.TERSE; // m_log.DebugFormat( // "[SCENE OBJECT PART]: Scheduling terse update for {0}, {1} at {2}", // UUID, Name, TimeStampTerse); } if (ParentGroup.Scene != null) ParentGroup.Scene.EventManager.TriggerSceneObjectPartUpdated(this, false); } public void ScriptSetPhysicsStatus(bool UsePhysics) { ParentGroup.ScriptSetPhysicsStatus(UsePhysics); } /// /// Set sculpt and mesh data, and tell the physics engine to process the change. /// /// The mesh itself. /* public void SculptTextureCallback(AssetBase texture) { if (m_shape.SculptEntry) { // commented out for sculpt map caching test - null could mean a cached sculpt map has been found //if (texture != null) { if (texture != null) { // m_log.DebugFormat( // "[SCENE OBJECT PART]: Setting sculpt data for {0} on SculptTextureCallback()", Name); m_shape.SculptData = texture.Data; } PhysicsActor pa = PhysActor; if (pa != null) { // Update the physics actor with the new loaded sculpt data and set the taint signal. pa.Shape = m_shape; ParentGroup.Scene.PhysicsScene.AddPhysicsActorTaint(pa); } } } } */ /// /// Send a full update to the client for the given part /// /// protected internal void SendFullUpdate(IClientAPI remoteClient) { if (ParentGroup == null) return; // m_log.DebugFormat( // "[SOG]: Sendinging part full update to {0} for {1} {2}", remoteClient.Name, part.Name, part.LocalId); if (ParentGroup.IsAttachment) { ScenePresence sp = ParentGroup.Scene.GetScenePresence(ParentGroup.AttachedAvatar); if (sp != null) { sp.SendAttachmentUpdate(this, UpdateRequired.FULL); } } /* this does nothing SendFullUpdateToClient(remoteClient, Position) ignores position parameter if (IsRoot) { if (ParentGroup.IsAttachment) { SendFullUpdateToClient(remoteClient, AttachedPos); } else { SendFullUpdateToClient(remoteClient, AbsolutePosition); } } */ else { SendFullUpdateToClient(remoteClient); } } /// /// Send a full update for this part to all clients. /// public void SendFullUpdateToAllClientsInternal() { if (ParentGroup == null) return; // Update the "last" values m_lastPosition = OffsetPosition; m_lastRotation = RotationOffset; m_lastVelocity = Velocity; m_lastAcceleration = Acceleration; m_lastAngularVelocity = AngularVelocity; m_lastUpdateSentTime = Environment.TickCount; ParentGroup.Scene.ForEachScenePresence(delegate(ScenePresence avatar) { SendFullUpdate(avatar.ControllingClient); }); } public void SendFullUpdateToAllClients() { if (ParentGroup == null) return; // Update the "last" values m_lastPosition = OffsetPosition; m_lastRotation = RotationOffset; m_lastVelocity = Velocity; m_lastAcceleration = Acceleration; m_lastAngularVelocity = AngularVelocity; m_lastUpdateSentTime = Environment.TickCount; if (ParentGroup.IsAttachment) { ScenePresence sp = ParentGroup.Scene.GetScenePresence(ParentGroup.AttachedAvatar); if (sp != null) { sp.SendAttachmentUpdate(this, UpdateRequired.FULL); } } else { ParentGroup.Scene.ForEachScenePresence(delegate(ScenePresence avatar) { SendFullUpdate(avatar.ControllingClient); }); } } /// /// Sends a full update to the client /// /// public void SendFullUpdateToClient(IClientAPI remoteClient) { SendFullUpdateToClient(remoteClient, OffsetPosition); } /// /// Sends a full update to the client /// /// /// public void SendFullUpdateToClient(IClientAPI remoteClient, Vector3 lPos) { if (ParentGroup == null) return; // Suppress full updates during attachment editing // sl Does send them // if (ParentGroup.IsSelected && ParentGroup.IsAttachment) // return; if (ParentGroup.IsDeleted) return; if (ParentGroup.IsAttachment && ParentGroup.AttachedAvatar != remoteClient.AgentId && ParentGroup.HasPrivateAttachmentPoint) return; if (remoteClient.AgentId == OwnerID) { if ((Flags & PrimFlags.CreateSelected) != 0) Flags &= ~PrimFlags.CreateSelected; } //bool isattachment = IsAttachment; //if (LocalId != ParentGroup.RootPart.LocalId) //isattachment = ParentGroup.RootPart.IsAttachment; remoteClient.SendEntityUpdate(this, PrimUpdateFlags.FullUpdate); ParentGroup.Scene.StatsReporter.AddObjectUpdates(1); } /// /// Tell all the prims which have had updates scheduled /// public void SendScheduledUpdates() { const float ROTATION_TOLERANCE = 0.01f; const float VELOCITY_TOLERANCE = 0.001f; const float POSITION_TOLERANCE = 0.05f; // I don't like this, but I suppose it's necessary const int TIME_MS_TOLERANCE = 200; //llSetPos has a 200ms delay. This should NOT be 3 seconds. switch (UpdateFlag) { case UpdateRequired.TERSE: { ClearUpdateSchedule(); // Throw away duplicate or insignificant updates if (!RotationOffset.ApproxEquals(m_lastRotation, ROTATION_TOLERANCE) || !Acceleration.Equals(m_lastAcceleration) || !Velocity.ApproxEquals(m_lastVelocity, VELOCITY_TOLERANCE) || Velocity.ApproxEquals(Vector3.Zero, VELOCITY_TOLERANCE) || !AngularVelocity.ApproxEquals(m_lastAngularVelocity, VELOCITY_TOLERANCE) || !OffsetPosition.ApproxEquals(m_lastPosition, POSITION_TOLERANCE) || Environment.TickCount - m_lastUpdateSentTime > TIME_MS_TOLERANCE) { SendTerseUpdateToAllClientsInternal(); } break; } case UpdateRequired.FULL: { ClearUpdateSchedule(); SendFullUpdateToAllClientsInternal(); break; } } } /// /// Send a terse update to all clients /// public void SendTerseUpdateToAllClientsInternal() { if (ParentGroup == null || ParentGroup.Scene == null) return; // Update the "last" values m_lastPosition = OffsetPosition; m_lastRotation = RotationOffset; m_lastVelocity = Velocity; m_lastAcceleration = Acceleration; m_lastAngularVelocity = AngularVelocity; m_lastUpdateSentTime = Environment.TickCount; ParentGroup.Scene.ForEachClient(delegate(IClientAPI client) { SendTerseUpdateToClient(client); }); } public void SendTerseUpdateToAllClients() { if (ParentGroup == null || ParentGroup.Scene == null) return; // Update the "last" values m_lastPosition = OffsetPosition; m_lastRotation = RotationOffset; m_lastVelocity = Velocity; m_lastAcceleration = Acceleration; m_lastAngularVelocity = AngularVelocity; m_lastUpdateSentTime = Environment.TickCount; if (ParentGroup.IsAttachment) { ScenePresence sp = ParentGroup.Scene.GetScenePresence(ParentGroup.AttachedAvatar); if (sp != null) { sp.SendAttachmentUpdate(this, UpdateRequired.TERSE); } } else { ParentGroup.Scene.ForEachClient(delegate(IClientAPI client) { SendTerseUpdateToClient(client); }); } } public void SetAxisRotation(int axis, int rotate) { ParentGroup.SetAxisRotation(axis, rotate); //Cannot use ScriptBaseClass constants as no referance to it currently. if ((axis & (int)SceneObjectGroup.axisSelect.STATUS_ROTATE_X) != 0) STATUS_ROTATE_X = rotate; if ((axis & (int)SceneObjectGroup.axisSelect.STATUS_ROTATE_Y) != 0) STATUS_ROTATE_Y = rotate; if ((axis & (int)SceneObjectGroup.axisSelect.STATUS_ROTATE_Z) != 0) STATUS_ROTATE_Z = rotate; } public void SetBuoyancy(float fvalue) { Buoyancy = fvalue; /* if (PhysActor != null) { PhysActor.Buoyancy = fvalue; } */ } public void SetDieAtEdge(bool p) { if (ParentGroup.IsDeleted) return; ParentGroup.RootPart.DIE_AT_EDGE = p; } public void SetFloatOnWater(int floatYN) { PhysicsActor pa = PhysActor; if (pa != null) pa.FloatOnWater = (floatYN == 1); } public void SetForce(Vector3 force) { Force = force; } public SOPVehicle VehicleParams { get { return m_vehicleParams; } set { m_vehicleParams = value; } } public int VehicleType { get { if (m_vehicleParams == null) return (int)Vehicle.TYPE_NONE; else return (int)m_vehicleParams.Type; } set { SetVehicleType(value); } } public void SetVehicleType(int type) { m_vehicleParams = null; if (type == (int)Vehicle.TYPE_NONE) { if (_parentID ==0 && PhysActor != null) PhysActor.VehicleType = (int)Vehicle.TYPE_NONE; return; } m_vehicleParams = new SOPVehicle(); m_vehicleParams.ProcessTypeChange((Vehicle)type); { if (_parentID ==0 && PhysActor != null) PhysActor.VehicleType = type; return; } } public void SetVehicleFlags(int param, bool remove) { if (m_vehicleParams == null) return; m_vehicleParams.ProcessVehicleFlags(param, remove); if (_parentID ==0 && PhysActor != null) { PhysActor.VehicleFlags(param, remove); } } public void SetVehicleFloatParam(int param, float value) { if (m_vehicleParams == null) return; m_vehicleParams.ProcessFloatVehicleParam((Vehicle)param, value); if (_parentID == 0 && PhysActor != null) { PhysActor.VehicleFloatParam(param, value); } } public void SetVehicleVectorParam(int param, Vector3 value) { if (m_vehicleParams == null) return; m_vehicleParams.ProcessVectorVehicleParam((Vehicle)param, value); if (_parentID == 0 && PhysActor != null) { PhysActor.VehicleVectorParam(param, value); } } public void SetVehicleRotationParam(int param, Quaternion rotation) { if (m_vehicleParams == null) return; m_vehicleParams.ProcessRotationVehicleParam((Vehicle)param, rotation); if (_parentID == 0 && PhysActor != null) { PhysActor.VehicleRotationParam(param, rotation); } } /// /// Set the color & alpha of prim faces /// /// /// /// public void SetFaceColorAlpha(int face, Vector3 color, double ?alpha) { Vector3 clippedColor = Util.Clip(color, 0.0f, 1.0f); float clippedAlpha = alpha.HasValue ? Util.Clip((float)alpha.Value, 0.0f, 1.0f) : 0; // The only way to get a deep copy/ If we don't do this, we can // never detect color changes further down. Byte[] buf = Shape.Textures.GetBytes(); Primitive.TextureEntry tex = new Primitive.TextureEntry(buf, 0, buf.Length); Color4 texcolor; if (face >= 0 && face < GetNumberOfSides()) { texcolor = tex.CreateFace((uint)face).RGBA; texcolor.R = clippedColor.X; texcolor.G = clippedColor.Y; texcolor.B = clippedColor.Z; if (alpha.HasValue) { texcolor.A = clippedAlpha; } tex.FaceTextures[face].RGBA = texcolor; UpdateTextureEntry(tex.GetBytes()); return; } else if (face == ALL_SIDES) { for (uint i = 0; i < GetNumberOfSides(); i++) { if (tex.FaceTextures[i] != null) { texcolor = tex.FaceTextures[i].RGBA; texcolor.R = clippedColor.X; texcolor.G = clippedColor.Y; texcolor.B = clippedColor.Z; if (alpha.HasValue) { texcolor.A = clippedAlpha; } tex.FaceTextures[i].RGBA = texcolor; } texcolor = tex.DefaultTexture.RGBA; texcolor.R = clippedColor.X; texcolor.G = clippedColor.Y; texcolor.B = clippedColor.Z; if (alpha.HasValue) { texcolor.A = clippedAlpha; } tex.DefaultTexture.RGBA = texcolor; } UpdateTextureEntry(tex.GetBytes()); return; } } /// /// Get the number of sides that this part has. /// /// public int GetNumberOfSides() { int ret = 0; bool hasCut; bool hasHollow; bool hasDimple; bool hasProfileCut; PrimType primType = GetPrimType(); HasCutHollowDimpleProfileCut(primType, Shape, out hasCut, out hasHollow, out hasDimple, out hasProfileCut); switch (primType) { case PrimType.BOX: ret = 6; if (hasCut) ret += 2; if (hasHollow) ret += 1; break; case PrimType.CYLINDER: ret = 3; if (hasCut) ret += 2; if (hasHollow) ret += 1; break; case PrimType.PRISM: ret = 5; if (hasCut) ret += 2; if (hasHollow) ret += 1; break; case PrimType.SPHERE: ret = 1; if (hasCut) ret += 2; if (hasDimple) ret += 2; if (hasHollow) ret += 1; break; case PrimType.TORUS: ret = 1; if (hasCut) ret += 2; if (hasProfileCut) ret += 2; if (hasHollow) ret += 1; break; case PrimType.TUBE: ret = 4; if (hasCut) ret += 2; if (hasProfileCut) ret += 2; if (hasHollow) ret += 1; break; case PrimType.RING: ret = 3; if (hasCut) ret += 2; if (hasProfileCut) ret += 2; if (hasHollow) ret += 1; break; case PrimType.SCULPT: // Special mesh handling if (Shape.SculptType == (byte)SculptType.Mesh) ret = 8; // if it's a mesh then max 8 faces else ret = 1; // if it's a sculpt then max 1 face break; } return ret; } /// /// Tell us what type this prim is /// /// /// public PrimType GetPrimType() { if (Shape.SculptEntry) return PrimType.SCULPT; if ((Shape.ProfileCurve & 0x07) == (byte)ProfileShape.Square) { if (Shape.PathCurve == (byte)Extrusion.Straight) return PrimType.BOX; else if (Shape.PathCurve == (byte)Extrusion.Curve1) return PrimType.TUBE; } else if ((Shape.ProfileCurve & 0x07) == (byte)ProfileShape.Circle) { if (Shape.PathCurve == (byte)Extrusion.Straight) return PrimType.CYLINDER; // ProfileCurve seems to combine hole shape and profile curve so we need to only compare against the lower 3 bits else if (Shape.PathCurve == (byte)Extrusion.Curve1) return PrimType.TORUS; } else if ((Shape.ProfileCurve & 0x07) == (byte)ProfileShape.HalfCircle) { if (Shape.PathCurve == (byte)Extrusion.Curve1 || Shape.PathCurve == (byte)Extrusion.Curve2) return PrimType.SPHERE; } else if ((Shape.ProfileCurve & 0x07) == (byte)ProfileShape.EquilateralTriangle) { if (Shape.PathCurve == (byte)Extrusion.Straight) return PrimType.PRISM; else if (Shape.PathCurve == (byte)Extrusion.Curve1) return PrimType.RING; } return PrimType.BOX; } /// /// Tell us if this object has cut, hollow, dimple, and other factors affecting the number of faces /// /// /// /// /// /// /// protected static void HasCutHollowDimpleProfileCut(PrimType primType, PrimitiveBaseShape shape, out bool hasCut, out bool hasHollow, out bool hasDimple, out bool hasProfileCut) { if (primType == PrimType.BOX || primType == PrimType.CYLINDER || primType == PrimType.PRISM) hasCut = (shape.ProfileBegin > 0) || (shape.ProfileEnd > 0); else hasCut = (shape.PathBegin > 0) || (shape.PathEnd > 0); hasHollow = shape.ProfileHollow > 0; hasDimple = (shape.ProfileBegin > 0) || (shape.ProfileEnd > 0); // taken from llSetPrimitiveParms hasProfileCut = hasDimple; // is it the same thing? } public void SetGroup(UUID groupID, IClientAPI client) { // Scene.AddNewPrims() calls with client == null so can't use this. // m_log.DebugFormat( // "[SCENE OBJECT PART]: Setting group for {0} to {1} for {2}", // Name, groupID, OwnerID); GroupID = groupID; // if (client != null) // SendPropertiesToClient(client); UpdateFlag = UpdateRequired.FULL; } /// /// Set the parent group of this prim. /// public void SetParent(SceneObjectGroup parent) { ParentGroup = parent; } // Use this for attachments! LocalID should be avatar's localid public void SetParentLocalId(uint localID) { ParentID = localID; } public void SetPhysicsAxisRotation() { PhysicsActor pa = PhysActor; if (pa != null) { pa.LockAngularMotion(RotationAxis); ParentGroup.Scene.PhysicsScene.AddPhysicsActorTaint(pa); } } /// /// Set the events that this part will pass on to listeners. /// /// /// public void SetScriptEvents(UUID scriptid, int events) { // m_log.DebugFormat( // "[SCENE OBJECT PART]: Set script events for script with id {0} on {1}/{2} to {3} in {4}", // scriptid, Name, ParentGroup.Name, events, ParentGroup.Scene.Name); // scriptEvents oldparts; lock (m_scriptEvents) { if (m_scriptEvents.ContainsKey(scriptid)) { // oldparts = m_scriptEvents[scriptid]; // remove values from aggregated script events if (m_scriptEvents[scriptid] == (scriptEvents) events) return; m_scriptEvents[scriptid] = (scriptEvents) events; } else { m_scriptEvents.Add(scriptid, (scriptEvents) events); } } aggregateScriptEvents(); } /// /// Set the text displayed for this part. /// /// public void SetText(string text) { Text = text; if (ParentGroup != null) { ParentGroup.HasGroupChanged = true; ScheduleFullUpdate(); } } /// /// Set the text displayed for this part. /// /// /// /// public void SetText(string text, Vector3 color, double alpha) { Color = Color.FromArgb((int) (alpha*0xff), (int) (color.X*0xff), (int) (color.Y*0xff), (int) (color.Z*0xff)); SetText(text); } public void StopMoveToTarget() { ParentGroup.StopMoveToTarget(); } public void StoreUndoState(ObjectChangeType change) { if (m_UndoRedo == null) m_UndoRedo = new UndoRedoState(5); lock (m_UndoRedo) { if (!Undoing && !IgnoreUndoUpdate && ParentGroup != null) // just to read better - undo is in progress, or suspended { m_UndoRedo.StoreUndo(this, change); } } } /// /// Return number of undos on the stack. Here temporarily pending a refactor. /// public int UndoCount { get { if (m_UndoRedo == null) return 0; return m_UndoRedo.Count; } } public void Undo() { if (m_UndoRedo == null || Undoing || ParentGroup == null) return; lock (m_UndoRedo) { Undoing = true; m_UndoRedo.Undo(this); Undoing = false; } } public void Redo() { if (m_UndoRedo == null || Undoing || ParentGroup == null) return; lock (m_UndoRedo) { Undoing = true; m_UndoRedo.Redo(this); Undoing = false; } } public void ClearUndoState() { if (m_UndoRedo == null || Undoing) return; lock (m_UndoRedo) { m_UndoRedo.Clear(); } } public EntityIntersection TestIntersection(Ray iray, Quaternion parentrot) { // In this case we're using a sphere with a radius of the largest dimension of the prim // TODO: Change to take shape into account EntityIntersection result = new EntityIntersection(); Vector3 vAbsolutePosition = AbsolutePosition; Vector3 vScale = Scale; Vector3 rOrigin = iray.Origin; Vector3 rDirection = iray.Direction; //rDirection = rDirection.Normalize(); // Buidling the first part of the Quadratic equation Vector3 r2ndDirection = rDirection*rDirection; float itestPart1 = r2ndDirection.X + r2ndDirection.Y + r2ndDirection.Z; // Buidling the second part of the Quadratic equation Vector3 tmVal2 = rOrigin - vAbsolutePosition; Vector3 r2Direction = rDirection*2.0f; Vector3 tmVal3 = r2Direction*tmVal2; float itestPart2 = tmVal3.X + tmVal3.Y + tmVal3.Z; // Buidling the third part of the Quadratic equation Vector3 tmVal4 = rOrigin*rOrigin; Vector3 tmVal5 = vAbsolutePosition*vAbsolutePosition; Vector3 tmVal6 = vAbsolutePosition*rOrigin; // Set Radius to the largest dimension of the prim float radius = 0f; if (vScale.X > radius) radius = vScale.X; if (vScale.Y > radius) radius = vScale.Y; if (vScale.Z > radius) radius = vScale.Z; // the second part of this is the default prim size // once we factor in the aabb of the prim we're adding we can // change this to; // radius = (radius / 2) - 0.01f; // radius = (radius / 2) + (0.5f / 2) - 0.1f; //radius = radius; float itestPart3 = tmVal4.X + tmVal4.Y + tmVal4.Z + tmVal5.X + tmVal5.Y + tmVal5.Z - (2.0f*(tmVal6.X + tmVal6.Y + tmVal6.Z + (radius*radius))); // Yuk Quadradrics.. Solve first float rootsqr = (itestPart2*itestPart2) - (4.0f*itestPart1*itestPart3); if (rootsqr < 0.0f) { // No intersection return result; } float root = ((-itestPart2) - (float) Math.Sqrt((double) rootsqr))/(itestPart1*2.0f); if (root < 0.0f) { // perform second quadratic root solution root = ((-itestPart2) + (float) Math.Sqrt((double) rootsqr))/(itestPart1*2.0f); // is there any intersection? if (root < 0.0f) { // nope, no intersection return result; } } // We got an intersection. putting together an EntityIntersection object with the // intersection information Vector3 ipoint = new Vector3(iray.Origin.X + (iray.Direction.X*root), iray.Origin.Y + (iray.Direction.Y*root), iray.Origin.Z + (iray.Direction.Z*root)); result.HitTF = true; result.ipoint = ipoint; // Normal is calculated by the difference and then normalizing the result Vector3 normalpart = ipoint - vAbsolutePosition; result.normal = normalpart / normalpart.Length(); // It's funny how the Vector3 object has a Distance function, but the Axiom.Math object doesn't. // I can write a function to do it.. but I like the fact that this one is Static. Vector3 distanceConvert1 = new Vector3(iray.Origin.X, iray.Origin.Y, iray.Origin.Z); Vector3 distanceConvert2 = new Vector3(ipoint.X, ipoint.Y, ipoint.Z); float distance = (float) Util.GetDistanceTo(distanceConvert1, distanceConvert2); result.distance = distance; return result; } public EntityIntersection TestIntersectionOBB(Ray iray, Quaternion parentrot, bool frontFacesOnly, bool faceCenters) { // In this case we're using a rectangular prism, which has 6 faces and therefore 6 planes // This breaks down into the ray---> plane equation. // TODO: Change to take shape into account Vector3[] vertexes = new Vector3[8]; // float[] distance = new float[6]; Vector3[] FaceA = new Vector3[6]; // vertex A for Facei Vector3[] FaceB = new Vector3[6]; // vertex B for Facei Vector3[] FaceC = new Vector3[6]; // vertex C for Facei Vector3[] FaceD = new Vector3[6]; // vertex D for Facei Vector3[] normals = new Vector3[6]; // Normal for Facei Vector3[] AAfacenormals = new Vector3[6]; // Axis Aligned face normals AAfacenormals[0] = new Vector3(1, 0, 0); AAfacenormals[1] = new Vector3(0, 1, 0); AAfacenormals[2] = new Vector3(-1, 0, 0); AAfacenormals[3] = new Vector3(0, -1, 0); AAfacenormals[4] = new Vector3(0, 0, 1); AAfacenormals[5] = new Vector3(0, 0, -1); Vector3 AmBa = new Vector3(0, 0, 0); // Vertex A - Vertex B Vector3 AmBb = new Vector3(0, 0, 0); // Vertex B - Vertex C Vector3 cross = new Vector3(); Vector3 pos = GetWorldPosition(); Quaternion rot = GetWorldRotation(); // Variables prefixed with AX are Axiom.Math copies of the LL variety. Quaternion AXrot = rot; AXrot.Normalize(); Vector3 AXpos = pos; // tScale is the offset to derive the vertex based on the scale. // it's different for each vertex because we've got to rotate it // to get the world position of the vertex to produce the Oriented Bounding Box Vector3 tScale = Vector3.Zero; Vector3 AXscale = new Vector3(m_shape.Scale.X * 0.5f, m_shape.Scale.Y * 0.5f, m_shape.Scale.Z * 0.5f); //Vector3 pScale = (AXscale) - (AXrot.Inverse() * (AXscale)); //Vector3 nScale = (AXscale * -1) - (AXrot.Inverse() * (AXscale * -1)); // rScale is the rotated offset to find a vertex based on the scale and the world rotation. Vector3 rScale = new Vector3(); // Get Vertexes for Faces Stick them into ABCD for each Face // Form: Face[face] that corresponds to the below diagram #region ABCD Face Vertex Map Comment Diagram // A _________ B // | | // | 4 top | // |_________| // C D // A _________ B // | Back | // | 3 | // |_________| // C D // A _________ B B _________ A // | Left | | Right | // | 0 | | 2 | // |_________| |_________| // C D D C // A _________ B // | Front | // | 1 | // |_________| // C D // C _________ D // | | // | 5 bot | // |_________| // A B #endregion #region Plane Decomposition of Oriented Bounding Box tScale = new Vector3(AXscale.X, -AXscale.Y, AXscale.Z); rScale = tScale * AXrot; vertexes[0] = (new Vector3((pos.X + rScale.X), (pos.Y + rScale.Y), (pos.Z + rScale.Z))); // vertexes[0].X = pos.X + vertexes[0].X; //vertexes[0].Y = pos.Y + vertexes[0].Y; //vertexes[0].Z = pos.Z + vertexes[0].Z; FaceA[0] = vertexes[0]; FaceB[3] = vertexes[0]; FaceA[4] = vertexes[0]; tScale = AXscale; rScale = tScale * AXrot; vertexes[1] = (new Vector3((pos.X + rScale.X), (pos.Y + rScale.Y), (pos.Z + rScale.Z))); // vertexes[1].X = pos.X + vertexes[1].X; // vertexes[1].Y = pos.Y + vertexes[1].Y; //vertexes[1].Z = pos.Z + vertexes[1].Z; FaceB[0] = vertexes[1]; FaceA[1] = vertexes[1]; FaceC[4] = vertexes[1]; tScale = new Vector3(AXscale.X, -AXscale.Y, -AXscale.Z); rScale = tScale * AXrot; vertexes[2] = (new Vector3((pos.X + rScale.X), (pos.Y + rScale.Y), (pos.Z + rScale.Z))); //vertexes[2].X = pos.X + vertexes[2].X; //vertexes[2].Y = pos.Y + vertexes[2].Y; //vertexes[2].Z = pos.Z + vertexes[2].Z; FaceC[0] = vertexes[2]; FaceD[3] = vertexes[2]; FaceC[5] = vertexes[2]; tScale = new Vector3(AXscale.X, AXscale.Y, -AXscale.Z); rScale = tScale * AXrot; vertexes[3] = (new Vector3((pos.X + rScale.X), (pos.Y + rScale.Y), (pos.Z + rScale.Z))); //vertexes[3].X = pos.X + vertexes[3].X; // vertexes[3].Y = pos.Y + vertexes[3].Y; // vertexes[3].Z = pos.Z + vertexes[3].Z; FaceD[0] = vertexes[3]; FaceC[1] = vertexes[3]; FaceA[5] = vertexes[3]; tScale = new Vector3(-AXscale.X, AXscale.Y, AXscale.Z); rScale = tScale * AXrot; vertexes[4] = (new Vector3((pos.X + rScale.X), (pos.Y + rScale.Y), (pos.Z + rScale.Z))); // vertexes[4].X = pos.X + vertexes[4].X; // vertexes[4].Y = pos.Y + vertexes[4].Y; // vertexes[4].Z = pos.Z + vertexes[4].Z; FaceB[1] = vertexes[4]; FaceA[2] = vertexes[4]; FaceD[4] = vertexes[4]; tScale = new Vector3(-AXscale.X, AXscale.Y, -AXscale.Z); rScale = tScale * AXrot; vertexes[5] = (new Vector3((pos.X + rScale.X), (pos.Y + rScale.Y), (pos.Z + rScale.Z))); // vertexes[5].X = pos.X + vertexes[5].X; // vertexes[5].Y = pos.Y + vertexes[5].Y; // vertexes[5].Z = pos.Z + vertexes[5].Z; FaceD[1] = vertexes[5]; FaceC[2] = vertexes[5]; FaceB[5] = vertexes[5]; tScale = new Vector3(-AXscale.X, -AXscale.Y, AXscale.Z); rScale = tScale * AXrot; vertexes[6] = (new Vector3((pos.X + rScale.X), (pos.Y + rScale.Y), (pos.Z + rScale.Z))); // vertexes[6].X = pos.X + vertexes[6].X; // vertexes[6].Y = pos.Y + vertexes[6].Y; // vertexes[6].Z = pos.Z + vertexes[6].Z; FaceB[2] = vertexes[6]; FaceA[3] = vertexes[6]; FaceB[4] = vertexes[6]; tScale = new Vector3(-AXscale.X, -AXscale.Y, -AXscale.Z); rScale = tScale * AXrot; vertexes[7] = (new Vector3((pos.X + rScale.X), (pos.Y + rScale.Y), (pos.Z + rScale.Z))); // vertexes[7].X = pos.X + vertexes[7].X; // vertexes[7].Y = pos.Y + vertexes[7].Y; // vertexes[7].Z = pos.Z + vertexes[7].Z; FaceD[2] = vertexes[7]; FaceC[3] = vertexes[7]; FaceD[5] = vertexes[7]; #endregion // Get our plane normals for (int i = 0; i < 6; i++) { //m_log.Info("[FACECALCULATION]: FaceA[" + i + "]=" + FaceA[i] + " FaceB[" + i + "]=" + FaceB[i] + " FaceC[" + i + "]=" + FaceC[i] + " FaceD[" + i + "]=" + FaceD[i]); // Our Plane direction AmBa = FaceA[i] - FaceB[i]; AmBb = FaceB[i] - FaceC[i]; cross = Vector3.Cross(AmBb, AmBa); // normalize the cross product to get the normal. normals[i] = cross / cross.Length(); //m_log.Info("[NORMALS]: normals[ " + i + "]" + normals[i].ToString()); //distance[i] = (normals[i].X * AmBa.X + normals[i].Y * AmBa.Y + normals[i].Z * AmBa.Z) * -1; } EntityIntersection result = new EntityIntersection(); result.distance = 1024; float c = 0; float a = 0; float d = 0; Vector3 q = new Vector3(); #region OBB Version 2 Experiment //float fmin = 999999; //float fmax = -999999; //float s = 0; //for (int i=0;i<6;i++) //{ //s = iray.Direction.Dot(normals[i]); //d = normals[i].Dot(FaceB[i]); //if (s == 0) //{ //if (iray.Origin.Dot(normals[i]) > d) //{ //return result; //} // else //{ //continue; //} //} //a = (d - iray.Origin.Dot(normals[i])) / s; //if (iray.Direction.Dot(normals[i]) < 0) //{ //if (a > fmax) //{ //if (a > fmin) //{ //return result; //} //fmax = a; //} //} //else //{ //if (a < fmin) //{ //if (a < 0 || a < fmax) //{ //return result; //} //fmin = a; //} //} //} //if (fmax > 0) // a= fmax; //else // a=fmin; //q = iray.Origin + a * iray.Direction; #endregion // Loop over faces (6 of them) for (int i = 0; i < 6; i++) { AmBa = FaceA[i] - FaceB[i]; AmBb = FaceB[i] - FaceC[i]; d = Vector3.Dot(normals[i], FaceB[i]); //if (faceCenters) //{ // c = normals[i].Dot(normals[i]); //} //else //{ c = Vector3.Dot(iray.Direction, normals[i]); //} if (c == 0) continue; a = (d - Vector3.Dot(iray.Origin, normals[i])) / c; if (a < 0) continue; // If the normal is pointing outside the object if (Vector3.Dot(iray.Direction, normals[i]) < 0 || !frontFacesOnly) { //if (faceCenters) //{ //(FaceA[i] + FaceB[i] + FaceC[1] + FaceD[i]) / 4f; // q = iray.Origin + a * normals[i]; //} //else //{ q = iray.Origin + iray.Direction * a; //} float distance2 = (float)GetDistanceTo(q, AXpos); // Is this the closest hit to the object's origin? //if (faceCenters) //{ // distance2 = (float)GetDistanceTo(q, iray.Origin); //} if (distance2 < result.distance) { result.distance = distance2; result.HitTF = true; result.ipoint = q; result.face = i; //m_log.Info("[FACE]:" + i.ToString()); //m_log.Info("[POINT]: " + q.ToString()); //m_log.Info("[DIST]: " + distance2.ToString()); if (faceCenters) { result.normal = AAfacenormals[i] * AXrot; Vector3 scaleComponent = AAfacenormals[i]; float ScaleOffset = 0.5f; if (scaleComponent.X != 0) ScaleOffset = AXscale.X; if (scaleComponent.Y != 0) ScaleOffset = AXscale.Y; if (scaleComponent.Z != 0) ScaleOffset = AXscale.Z; ScaleOffset = Math.Abs(ScaleOffset); Vector3 offset = result.normal * ScaleOffset; result.ipoint = AXpos + offset; ///pos = (intersectionpoint + offset); } else { result.normal = normals[i]; } result.AAfaceNormal = AAfacenormals[i]; } } } return result; } /// /// Serialize this part to xml. /// /// public void ToXml(XmlTextWriter xmlWriter) { SceneObjectSerializer.SOPToXml2(xmlWriter, this, new Dictionary()); } public void TriggerScriptChangedEvent(Changed val) { if (ParentGroup != null && ParentGroup.Scene != null) ParentGroup.Scene.EventManager.TriggerOnScriptChangedEvent(LocalId, (uint)val); } public void TrimPermissions() { BaseMask &= (uint)(PermissionMask.All | PermissionMask.Export); OwnerMask &= (uint)(PermissionMask.All | PermissionMask.Export); GroupMask &= (uint)PermissionMask.All; EveryoneMask &= (uint)(PermissionMask.All | PermissionMask.Export); NextOwnerMask &= (uint)PermissionMask.All; } public void UpdateExtraParam(ushort type, bool inUse, byte[] data) { m_shape.ReadInUpdateExtraParam(type, inUse, data); /* if (type == 0x30) { if (m_shape.SculptEntry && m_shape.SculptTexture != UUID.Zero) { ParentGroup.Scene.AssetService.Get(m_shape.SculptTexture.ToString(), this, AssetReceived); } } */ if (ParentGroup != null) { ParentGroup.HasGroupChanged = true; ScheduleFullUpdate(); } } public void UpdateGroupPosition(Vector3 newPos) { Vector3 oldPos = GroupPosition; if ((newPos.X != oldPos.X) || (newPos.Y != oldPos.Y) || (newPos.Z != oldPos.Z)) { GroupPosition = newPos; ScheduleTerseUpdate(); } } /// /// Update this part's offset position. /// /// public void UpdateOffSet(Vector3 newPos) { Vector3 oldPos = OffsetPosition; if ((newPos.X != oldPos.X) || (newPos.Y != oldPos.Y) || (newPos.Z != oldPos.Z)) { if (ParentGroup.RootPart.GetStatusSandbox()) { if (Util.GetDistanceTo(ParentGroup.RootPart.StatusSandboxPos, newPos) > 10) { ParentGroup.RootPart.ScriptSetPhysicsStatus(false); newPos = OffsetPosition; ParentGroup.Scene.SimChat(Utils.StringToBytes("Hit Sandbox Limit"), ChatTypeEnum.DebugChannel, 0x7FFFFFFF, ParentGroup.RootPart.AbsolutePosition, Name, UUID, false); } } OffsetPosition = newPos; ScheduleTerseUpdate(); } } /// /// Update permissions on the SOP. Should only be called from SOG.UpdatePermissions because the SOG /// will handle the client notifications once all of its parts are updated. /// /// /// /// /// /// public void UpdatePermissions(UUID AgentID, byte field, uint localID, uint mask, byte addRemTF) { bool set = addRemTF == 1; bool god = ParentGroup.Scene.Permissions.IsGod(AgentID); uint baseMask = BaseMask; if (god) baseMask = 0x7ffffff0; // Are we the owner? if ((AgentID == OwnerID) || god) { switch (field) { case 1: if (god) { BaseMask = ApplyMask(BaseMask, set, mask); Inventory.ApplyGodPermissions(BaseMask); } break; case 2: OwnerMask = ApplyMask(OwnerMask, set, mask) & baseMask; break; case 4: GroupMask = ApplyMask(GroupMask, set, mask) & baseMask; break; case 8: // Trying to set export permissions - extra checks if (set && (mask & (uint)PermissionMask.Export) != 0) { if ((OwnerMask & (uint)PermissionMask.Export) == 0 || (BaseMask & (uint)PermissionMask.Export) == 0 || (NextOwnerMask & (uint)PermissionMask.All) != (uint)PermissionMask.All) mask &= ~(uint)PermissionMask.Export; } EveryoneMask = ApplyMask(EveryoneMask, set, mask) & baseMask; break; case 16: // Force full perm if export if ((EveryoneMask & (uint)PermissionMask.Export) != 0) { NextOwnerMask = (uint)PermissionMask.All; break; } NextOwnerMask = ApplyMask(NextOwnerMask, set, mask) & baseMask; // Prevent the client from creating no copy, no transfer // objects if ((NextOwnerMask & (uint)PermissionMask.Copy) == 0) NextOwnerMask |= (uint)PermissionMask.Transfer; NextOwnerMask |= (uint)PermissionMask.Move; break; } SendFullUpdateToAllClients(); } } public void ClonePermissions(SceneObjectPart source) { uint prevOwnerMask = OwnerMask; uint prevGroupMask = GroupMask; uint prevEveryoneMask = EveryoneMask; uint prevNextOwnerMask = NextOwnerMask; OwnerMask = source.OwnerMask & BaseMask; GroupMask = source.GroupMask & BaseMask; EveryoneMask = source.EveryoneMask & BaseMask; NextOwnerMask = source.NextOwnerMask & BaseMask; if (OwnerMask != prevOwnerMask || GroupMask != prevGroupMask || EveryoneMask != prevEveryoneMask || NextOwnerMask != prevNextOwnerMask) SendFullUpdateToAllClients(); } public bool IsHingeJoint() { // For now, we use the NINJA naming scheme for identifying joints. // In the future, we can support other joint specification schemes such as a // custom checkbox in the viewer GUI. if (ParentGroup.Scene != null && ParentGroup.Scene.PhysicsScene.SupportsNINJAJoints) { string hingeString = "hingejoint"; return (Name.Length >= hingeString.Length && Name.Substring(0, hingeString.Length) == hingeString); } else { return false; } } public bool IsBallJoint() { // For now, we use the NINJA naming scheme for identifying joints. // In the future, we can support other joint specification schemes such as a // custom checkbox in the viewer GUI. if (ParentGroup.Scene != null && ParentGroup.Scene.PhysicsScene.SupportsNINJAJoints) { string ballString = "balljoint"; return (Name.Length >= ballString.Length && Name.Substring(0, ballString.Length) == ballString); } else { return false; } } public bool IsJoint() { // For now, we use the NINJA naming scheme for identifying joints. // In the future, we can support other joint specification schemes such as a // custom checkbox in the viewer GUI. if (ParentGroup.Scene != null && ParentGroup.Scene.PhysicsScene != null && ParentGroup.Scene.PhysicsScene.SupportsNINJAJoints) { return IsHingeJoint() || IsBallJoint(); } else { return false; } } public void UpdateExtraPhysics(ExtraPhysicsData physdata) { if (physdata.PhysShapeType == PhysShapeType.invalid || ParentGroup == null) return; if (PhysicsShapeType != (byte)physdata.PhysShapeType) { PhysicsShapeType = (byte)physdata.PhysShapeType; } if(Density != physdata.Density) Density = physdata.Density; if(GravityModifier != physdata.GravitationModifier) GravityModifier = physdata.GravitationModifier; if(Friction != physdata.Friction) Friction = physdata.Friction; if(Restitution != physdata.Bounce) Restitution = physdata.Bounce; } /// /// Update the flags on this prim. This covers properties such as phantom, physics and temporary. /// /// /// /// /// public void UpdatePrimFlags(bool UsePhysics, bool SetTemporary, bool SetPhantom, bool SetVD, bool building) { bool wasUsingPhysics = ((Flags & PrimFlags.Physics) != 0); bool wasTemporary = ((Flags & PrimFlags.TemporaryOnRez) != 0); bool wasPhantom = ((Flags & PrimFlags.Phantom) != 0); bool wasVD = VolumeDetectActive; if ((UsePhysics == wasUsingPhysics) && (wasTemporary == SetTemporary) && (wasPhantom == SetPhantom) && (SetVD == wasVD)) return; VolumeDetectActive = SetVD; // volume detector implies phantom if (VolumeDetectActive) SetPhantom = true; if (UsePhysics) AddFlag(PrimFlags.Physics); else RemFlag(PrimFlags.Physics); if (SetPhantom) AddFlag(PrimFlags.Phantom); else RemFlag(PrimFlags.Phantom); if (SetTemporary) AddFlag(PrimFlags.TemporaryOnRez); else RemFlag(PrimFlags.TemporaryOnRez); if (ParentGroup.Scene == null) return; PhysicsActor pa = PhysActor; if (pa != null && building && pa.Building != building) pa.Building = building; if ((SetPhantom && !UsePhysics && !SetVD) || ParentGroup.IsAttachment || PhysicsShapeType == (byte)PhysShapeType.none || (Shape.PathCurve == (byte)Extrusion.Flexible)) { if (pa != null) { if(wasUsingPhysics) ParentGroup.Scene.RemovePhysicalPrim(1); RemoveFromPhysics(); } Velocity = new Vector3(0, 0, 0); Acceleration = new Vector3(0, 0, 0); if (ParentGroup.RootPart == this) AngularVelocity = new Vector3(0, 0, 0); } else { if (ParentGroup.Scene.CollidablePrims) { if (pa == null) { AddToPhysics(UsePhysics, SetPhantom, building, false); pa = PhysActor; /* if (pa != null) { if ( // ((AggregateScriptEvents & scriptEvents.collision) != 0) || // ((AggregateScriptEvents & scriptEvents.collision_end) != 0) || // ((AggregateScriptEvents & scriptEvents.collision_start) != 0) || // ((AggregateScriptEvents & scriptEvents.land_collision_start) != 0) || // ((AggregateScriptEvents & scriptEvents.land_collision) != 0) || // ((AggregateScriptEvents & scriptEvents.land_collision_end) != 0) || ((AggregateScriptEvents & PhysicsNeededSubsEvents) != 0) || ((ParentGroup.RootPart.AggregateScriptEvents & PhysicsNeededSubsEvents) != 0) || (CollisionSound != UUID.Zero) ) { pa.OnCollisionUpdate += PhysicsCollision; pa.SubscribeEvents(1000); } } */ if (pa != null) { pa.SetMaterial(Material); DoPhysicsPropertyUpdate(UsePhysics, true); } } else // it already has a physical representation { DoPhysicsPropertyUpdate(UsePhysics, false); // Update physical status. /* moved into DoPhysicsPropertyUpdate if(VolumeDetectActive) pa.SetVolumeDetect(1); else pa.SetVolumeDetect(0); */ if (pa.Building != building) pa.Building = building; } UpdatePhysicsSubscribedEvents(); } } if (SetVD) { // If the above logic worked (this is urgent candidate to unit tests!) // we now have a physicsactor. // Defensive programming calls for a check here. // Better would be throwing an exception that could be catched by a unit test as the internal // logic should make sure, this Physactor is always here. if (pa != null) { pa.SetVolumeDetect(1); AddFlag(PrimFlags.Phantom); // We set this flag also if VD is active VolumeDetectActive = true; } // m_log.Debug("Update: PHY:" + UsePhysics.ToString() + ", T:" + IsTemporary.ToString() + ", PHA:" + IsPhantom.ToString() + " S:" + CastsShadows.ToString()); } else if (SetVD != wasVD) { // Remove VolumeDetect in any case. Note, it's safe to call SetVolumeDetect as often as you like // (mumbles, well, at least if you have infinte CPU powers :-)) if (pa != null) pa.SetVolumeDetect(0); RemFlag(PrimFlags.Phantom); VolumeDetectActive = false; } // and last in case we have a new actor and not building if (ParentGroup != null) { ParentGroup.HasGroupChanged = true; ScheduleFullUpdate(); } // m_log.DebugFormat("[SCENE OBJECT PART]: Updated PrimFlags on {0} {1} to {2}", Name, LocalId, Flags); } /// /// Adds this part to the physics scene. /// and sets the PhysActor property /// /// Add this prim as physical. /// Add this prim as phantom. /// tells physics to delay full construction of object /// applies velocities, force and torque private void AddToPhysics(bool isPhysical, bool isPhantom, bool building, bool applyDynamics) { PhysicsActor pa; Vector3 velocity = Velocity; Vector3 rotationalVelocity = AngularVelocity;; try { pa = ParentGroup.Scene.PhysicsScene.AddPrimShape( string.Format("{0}/{1}", Name, UUID), Shape, AbsolutePosition, Scale, GetWorldRotation(), isPhysical, isPhantom, PhysicsShapeType, m_localId); } catch (Exception e) { m_log.ErrorFormat("[SCENE]: caught exception meshing object {0}. Object set to phantom. e={1}", m_uuid, e); pa = null; } if (pa != null) { pa.SOPName = this.Name; // save object into the PhysActor so ODE internals know the joint/body info pa.SetMaterial(Material); pa.Density = Density; pa.GravModifier = GravityModifier; pa.Friction = Friction; pa.Restitution = Restitution; if (VolumeDetectActive) // change if not the default only pa.SetVolumeDetect(1); if (m_vehicleParams != null && LocalId == ParentGroup.RootPart.LocalId) m_vehicleParams.SetVehicle(pa); // we are going to tell rest of code about physics so better have this here PhysActor = pa; // DoPhysicsPropertyUpdate(isPhysical, true); // lets expand it here just with what it really needs to do if (isPhysical) { if (ParentGroup.RootPart.KeyframeMotion != null) ParentGroup.RootPart.KeyframeMotion.Stop(); ParentGroup.RootPart.KeyframeMotion = null; ParentGroup.Scene.AddPhysicalPrim(1); pa.OnRequestTerseUpdate += PhysicsRequestingTerseUpdate; pa.OnOutOfBounds += PhysicsOutOfBounds; if (ParentID != 0 && ParentID != LocalId) { PhysicsActor parentPa = ParentGroup.RootPart.PhysActor; if (parentPa != null) { pa.link(parentPa); } } } if (applyDynamics) // do independent of isphysical so parameters get setted (at least some) { Velocity = velocity; AngularVelocity = rotationalVelocity; // pa.Velocity = velocity; pa.RotationalVelocity = rotationalVelocity; // if not vehicle and root part apply force and torque if ((m_vehicleParams == null || m_vehicleParams.Type == Vehicle.TYPE_NONE) && LocalId == ParentGroup.RootPart.LocalId) { pa.Force = Force; pa.Torque = Torque; } } // if (Shape.SculptEntry) // CheckSculptAndLoad(); // else ParentGroup.Scene.PhysicsScene.AddPhysicsActorTaint(pa); if (!building) pa.Building = false; } PhysActor = pa; ParentGroup.Scene.EventManager.TriggerObjectAddedToPhysicalScene(this); } /// /// This removes the part from the physics scene. /// /// /// This isn't the same as turning off physical, since even without being physical the prim has a physics /// representation for collision detection. /// public void RemoveFromPhysics() { PhysicsActor pa = PhysActor; if (pa != null) { pa.OnCollisionUpdate -= PhysicsCollision; pa.OnRequestTerseUpdate -= PhysicsRequestingTerseUpdate; pa.OnOutOfBounds -= PhysicsOutOfBounds; ParentGroup.Scene.PhysicsScene.RemovePrim(pa); ParentGroup.Scene.EventManager.TriggerObjectRemovedFromPhysicalScene(this); } PhysActor = null; } /// /// This updates the part's rotation and sends out an update to clients if necessary. /// /// public void UpdateRotation(Quaternion rot) { if (rot != RotationOffset) { RotationOffset = rot; if (ParentGroup != null) { ParentGroup.HasGroupChanged = true; ScheduleTerseUpdate(); } } } /// /// Update the shape of this part. /// /// public void UpdateShape(ObjectShapePacket.ObjectDataBlock shapeBlock) { m_shape.PathBegin = shapeBlock.PathBegin; m_shape.PathEnd = shapeBlock.PathEnd; m_shape.PathScaleX = shapeBlock.PathScaleX; m_shape.PathScaleY = shapeBlock.PathScaleY; m_shape.PathShearX = shapeBlock.PathShearX; m_shape.PathShearY = shapeBlock.PathShearY; m_shape.PathSkew = shapeBlock.PathSkew; m_shape.ProfileBegin = shapeBlock.ProfileBegin; m_shape.ProfileEnd = shapeBlock.ProfileEnd; m_shape.PathCurve = shapeBlock.PathCurve; m_shape.ProfileCurve = shapeBlock.ProfileCurve; m_shape.ProfileHollow = shapeBlock.ProfileHollow; m_shape.PathRadiusOffset = shapeBlock.PathRadiusOffset; m_shape.PathRevolutions = shapeBlock.PathRevolutions; m_shape.PathTaperX = shapeBlock.PathTaperX; m_shape.PathTaperY = shapeBlock.PathTaperY; m_shape.PathTwist = shapeBlock.PathTwist; m_shape.PathTwistBegin = shapeBlock.PathTwistBegin; PhysicsActor pa = PhysActor; if (pa != null) { pa.Shape = m_shape; ParentGroup.Scene.PhysicsScene.AddPhysicsActorTaint(pa); } // This is what makes vehicle trailers work // A script in a child prim re-issues // llSetPrimitiveParams(PRIM_TYPE) every few seconds. That // prevents autoreturn. This is not well known. It also works // in SL. // if (ParentGroup.RootPart != this) ParentGroup.RootPart.Rezzed = DateTime.UtcNow; ParentGroup.HasGroupChanged = true; TriggerScriptChangedEvent(Changed.SHAPE); ScheduleFullUpdate(); } public void UpdateSlice(float begin, float end) { if (end < begin) { float temp = begin; begin = end; end = temp; } end = Math.Min(1f, Math.Max(0f, end)); begin = Math.Min(Math.Min(1f, Math.Max(0f, begin)), end - 0.02f); if (begin < 0.02f && end < 0.02f) { begin = 0f; end = 0.02f; } ushort uBegin = (ushort)(50000.0 * begin); ushort uEnd = (ushort)(50000.0 * (1f - end)); bool updatePossiblyNeeded = false; PrimType primType = GetPrimType(); if (primType == PrimType.SPHERE || primType == PrimType.TORUS || primType == PrimType.TUBE || primType == PrimType.RING) { if (m_shape.ProfileBegin != uBegin || m_shape.ProfileEnd != uEnd) { m_shape.ProfileBegin = uBegin; m_shape.ProfileEnd = uEnd; updatePossiblyNeeded = true; } } else if (m_shape.PathBegin != uBegin || m_shape.PathEnd != uEnd) { m_shape.PathBegin = uBegin; m_shape.PathEnd = uEnd; updatePossiblyNeeded = true; } if (updatePossiblyNeeded && ParentGroup != null) { ParentGroup.HasGroupChanged = true; } if (updatePossiblyNeeded && PhysActor != null) { PhysActor.Shape = m_shape; ParentGroup.Scene.PhysicsScene.AddPhysicsActorTaint(PhysActor); } if (updatePossiblyNeeded) { ScheduleFullUpdate(); } } /// /// If the part is a sculpt/mesh, retrieve the mesh data and reinsert it into the shape so that the physics /// engine can use it. /// /// /// When the physics engine has finished with it, the sculpt data is discarded to save memory. /// /* public void CheckSculptAndLoad() { // m_log.DebugFormat("Processing CheckSculptAndLoad for {0} {1}", Name, LocalId); return; if (ParentGroup.IsDeleted) return; if ((ParentGroup.RootPart.GetEffectiveObjectFlags() & (uint)PrimFlags.Phantom) != 0) return; if (Shape.SculptEntry && Shape.SculptTexture != UUID.Zero) { // check if a previously decoded sculpt map has been cached // We don't read the file here - the meshmerizer will do that later. // TODO: Could we simplify the meshmerizer code by reading and setting the data here? if (File.Exists(System.IO.Path.Combine("j2kDecodeCache", "smap_" + Shape.SculptTexture.ToString()))) { SculptTextureCallback(null); } else { ParentGroup.Scene.AssetService.Get(Shape.SculptTexture.ToString(), this, AssetReceived); } } } */ /// /// Update the texture entry for this part. /// /// public void UpdateTextureEntry(byte[] serializedTextureEntry) { UpdateTextureEntry(new Primitive.TextureEntry(serializedTextureEntry, 0, serializedTextureEntry.Length)); } /// /// Update the texture entry for this part. /// /// public void UpdateTextureEntry(Primitive.TextureEntry newTex) { Primitive.TextureEntry oldTex = Shape.Textures; Changed changeFlags = 0; Primitive.TextureEntryFace fallbackNewFace = newTex.DefaultTexture; Primitive.TextureEntryFace fallbackOldFace = oldTex.DefaultTexture; // On Incoming packets, sometimes newText.DefaultTexture is null. The assumption is that all // other prim-sides are set, but apparently that's not always the case. Lets assume packet/data corruption at this point. if (fallbackNewFace == null) { fallbackNewFace = new Primitive.TextureEntry(Util.BLANK_TEXTURE_UUID).CreateFace(0); newTex.DefaultTexture = fallbackNewFace; } if (fallbackOldFace == null) { fallbackOldFace = new Primitive.TextureEntry(Util.BLANK_TEXTURE_UUID).CreateFace(0); oldTex.DefaultTexture = fallbackOldFace; } // Materials capable viewers can send a ObjectImage packet // when nothing in TE has changed. MaterialID should be updated // by the RenderMaterials CAP handler, so updating it here may cause a // race condtion. Therefore, if no non-materials TE fields have changed, // we should ignore any changes and not update Shape.TextureEntry bool otherFieldsChanged = false; for (int i = 0 ; i < GetNumberOfSides(); i++) { Primitive.TextureEntryFace newFace = newTex.DefaultTexture; Primitive.TextureEntryFace oldFace = oldTex.DefaultTexture; if (oldTex.FaceTextures[i] != null) oldFace = oldTex.FaceTextures[i]; if (newTex.FaceTextures[i] != null) newFace = newTex.FaceTextures[i]; Color4 oldRGBA = oldFace.RGBA; Color4 newRGBA = newFace.RGBA; if (oldRGBA.R != newRGBA.R || oldRGBA.G != newRGBA.G || oldRGBA.B != newRGBA.B || oldRGBA.A != newRGBA.A) changeFlags |= Changed.COLOR; if (oldFace.TextureID != newFace.TextureID) changeFlags |= Changed.TEXTURE; // Max change, skip the rest of testing if (changeFlags == (Changed.TEXTURE | Changed.COLOR)) break; if (!otherFieldsChanged) { if (oldFace.Bump != newFace.Bump) otherFieldsChanged = true; if (oldFace.Fullbright != newFace.Fullbright) otherFieldsChanged = true; if (oldFace.Glow != newFace.Glow) otherFieldsChanged = true; if (oldFace.MediaFlags != newFace.MediaFlags) otherFieldsChanged = true; if (oldFace.OffsetU != newFace.OffsetU) otherFieldsChanged = true; if (oldFace.OffsetV != newFace.OffsetV) otherFieldsChanged = true; if (oldFace.RepeatU != newFace.RepeatU) otherFieldsChanged = true; if (oldFace.RepeatV != newFace.RepeatV) otherFieldsChanged = true; if (oldFace.Rotation != newFace.Rotation) otherFieldsChanged = true; if (oldFace.Shiny != newFace.Shiny) otherFieldsChanged = true; if (oldFace.TexMapType != newFace.TexMapType) otherFieldsChanged = true; } } if (changeFlags != 0 || otherFieldsChanged) { m_shape.TextureEntry = newTex.GetBytes(); if (changeFlags != 0) TriggerScriptChangedEvent(changeFlags); UpdateFlag = UpdateRequired.FULL; ParentGroup.HasGroupChanged = true; //This is madness.. //ParentGroup.ScheduleGroupForFullUpdate(); //This is sparta ScheduleFullUpdate(); } } private void UpdatePhysicsSubscribedEvents() { PhysicsActor pa = PhysActor; if (pa == null) return; pa.OnCollisionUpdate -= PhysicsCollision; bool hassound = (!VolumeDetectActive && CollisionSoundType >= 0 && ((Flags & PrimFlags.Physics) != 0)); scriptEvents CombinedEvents = AggregateScriptEvents; // merge with root part if (ParentGroup != null && ParentGroup.RootPart != null) CombinedEvents |= ParentGroup.RootPart.AggregateScriptEvents; // submit to this part case if (VolumeDetectActive) CombinedEvents &= PhyscicsVolumeDtcSubsEvents; else if ((Flags & PrimFlags.Phantom) != 0) CombinedEvents &= PhyscicsPhantonSubsEvents; else CombinedEvents &= PhysicsNeededSubsEvents; if (hassound || CombinedEvents != 0) { // subscribe to physics updates. pa.OnCollisionUpdate += PhysicsCollision; pa.SubscribeEvents(50); // 20 reports per second } else { pa.UnSubscribeEvents(); } } public void aggregateScriptEvents() { if (ParentGroup == null || ParentGroup.RootPart == null) return; AggregateScriptEvents = 0; // Aggregate script events lock (m_scriptEvents) { foreach (scriptEvents s in m_scriptEvents.Values) { AggregateScriptEvents |= s; } } uint objectflagupdate = 0; if ( ((AggregateScriptEvents & scriptEvents.touch) != 0) || ((AggregateScriptEvents & scriptEvents.touch_end) != 0) || ((AggregateScriptEvents & scriptEvents.touch_start) != 0) ) { objectflagupdate |= (uint) PrimFlags.Touch; } if ((AggregateScriptEvents & scriptEvents.money) != 0) { objectflagupdate |= (uint) PrimFlags.Money; } if (AllowedDrop) { objectflagupdate |= (uint) PrimFlags.AllowInventoryDrop; } /* PhysicsActor pa = PhysActor; if (pa != null) { if ( // ((AggregateScriptEvents & scriptEvents.collision) != 0) || // ((AggregateScriptEvents & scriptEvents.collision_end) != 0) || // ((AggregateScriptEvents & scriptEvents.collision_start) != 0) || // ((AggregateScriptEvents & scriptEvents.land_collision_start) != 0) || // ((AggregateScriptEvents & scriptEvents.land_collision) != 0) || // ((AggregateScriptEvents & scriptEvents.land_collision_end) != 0) || ((AggregateScriptEvents & PhysicsNeededSubsEvents) != 0) || ((ParentGroup.RootPart.AggregateScriptEvents & PhysicsNeededSubsEvents) != 0) || (CollisionSound != UUID.Zero) ) { // subscribe to physics updates. pa.OnCollisionUpdate += PhysicsCollision; pa.SubscribeEvents(1000); } else { pa.UnSubscribeEvents(); pa.OnCollisionUpdate -= PhysicsCollision; } } */ UpdatePhysicsSubscribedEvents(); //if ((GetEffectiveObjectFlags() & (uint)PrimFlags.Scripted) != 0) //{ // ParentGroup.Scene.EventManager.OnScriptTimerEvent += handleTimerAccounting; //} //else //{ // ParentGroup.Scene.EventManager.OnScriptTimerEvent -= handleTimerAccounting; //} LocalFlags = (PrimFlags)objectflagupdate; if (ParentGroup != null && ParentGroup.RootPart == this) { ParentGroup.aggregateScriptEvents(); } else { // m_log.DebugFormat( // "[SCENE OBJECT PART]: Scheduling part {0} {1} for full update in aggregateScriptEvents()", Name, LocalId); ScheduleFullUpdate(); } } public void SetCameraAtOffset(Vector3 v) { m_cameraAtOffset = v; } public void SetCameraEyeOffset(Vector3 v) { m_cameraEyeOffset = v; } public void SetForceMouselook(bool force) { m_forceMouselook = force; } public Vector3 GetCameraAtOffset() { return m_cameraAtOffset; } public Vector3 GetCameraEyeOffset() { return m_cameraEyeOffset; } public bool GetForceMouselook() { return m_forceMouselook; } public override string ToString() { return String.Format("{0} {1} (parent {2}))", Name, UUID, ParentGroup); } #endregion Public Methods public void SendTerseUpdateToClient(IClientAPI remoteClient) { if (ParentGroup.IsDeleted) return; if (ParentGroup.IsAttachment && (ParentGroup.RootPart != this || ParentGroup.AttachedAvatar != remoteClient.AgentId && ParentGroup.HasPrivateAttachmentPoint)) return; // Causes this thread to dig into the Client Thread Data. // Remember your locking here! remoteClient.SendEntityUpdate( this, PrimUpdateFlags.Position | PrimUpdateFlags.Rotation | PrimUpdateFlags.Velocity | PrimUpdateFlags.Acceleration | PrimUpdateFlags.AngularVelocity); ParentGroup.Scene.StatsReporter.AddObjectUpdates(1); } public void AddScriptLPS(int count) { ParentGroup.AddScriptLPS(count); } /// /// Sets a prim's owner and permissions when it's rezzed. /// /// The inventory item from which the item was rezzed /// True: the item is being rezzed from the user's inventory. False: from a prim's inventory. /// The scene the prim is being rezzed into public void ApplyPermissionsOnRez(InventoryItemBase item, bool userInventory, Scene scene) { if ((OwnerID != item.Owner) || ((item.CurrentPermissions & SceneObjectGroup.SLAM) != 0) || ((item.Flags & (uint)InventoryItemFlags.ObjectSlamPerm) != 0)) { if (scene.Permissions.PropagatePermissions()) { if ((item.Flags & (uint)InventoryItemFlags.ObjectHasMultipleItems) == 0) { // Apply the item's permissions to the object //LogPermissions("Before applying item permissions"); if (userInventory) { EveryoneMask = item.EveryOnePermissions; NextOwnerMask = item.NextPermissions; } else { if ((item.Flags & (uint)InventoryItemFlags.ObjectOverwriteEveryone) != 0) EveryoneMask = item.EveryOnePermissions; if ((item.Flags & (uint)InventoryItemFlags.ObjectOverwriteNextOwner) != 0) NextOwnerMask = item.NextPermissions; if ((item.Flags & (uint)InventoryItemFlags.ObjectOverwriteGroup) != 0) GroupMask = item.GroupPermissions; } //LogPermissions("After applying item permissions"); } } GroupMask = 0; // DO NOT propagate here } if (OwnerID != item.Owner) { //LogPermissions("Before ApplyNextOwnerPermissions"); if (scene.Permissions.PropagatePermissions()) ApplyNextOwnerPermissions(); //LogPermissions("After ApplyNextOwnerPermissions"); LastOwnerID = OwnerID; OwnerID = item.Owner; Inventory.ChangeInventoryOwner(item.Owner); } } /// /// Logs the prim's permissions. Useful when debugging permission problems. /// /// private void LogPermissions(String message) { PermissionsUtil.LogPermissions(Name, message, BaseMask, OwnerMask, NextOwnerMask); } public void ApplyNextOwnerPermissions() { // Export needs to be preserved in the base and everyone // mask, but removed in the owner mask as a next owner // can never change the export status BaseMask &= NextOwnerMask | (uint)PermissionMask.Export; OwnerMask &= NextOwnerMask; EveryoneMask &= NextOwnerMask | (uint)PermissionMask.Export; Inventory.ApplyNextOwnerPermissions(); } public void UpdateLookAt() { try { if (APIDActive) { PhysicsActor pa = ParentGroup.RootPart.PhysActor; if (pa == null || !pa.IsPhysical || APIDStrength < 0.04) { StopLookAt(); return; } Quaternion currRot = GetWorldRotation(); currRot.Normalize(); // difference between current orientation and desired orientation Quaternion dR = currRot / APIDTarget; // find axis and angle of rotation to rotate to desired orientation Vector3 axis = Vector3.UnitX; float angle; dR.GetAxisAngle(out axis, out angle); axis = axis * currRot; // clamp strength to avoid overshoot float strength = 1.0f / APIDStrength; if (strength > 1.0) strength = 1.0f; // set angular velocity to rotate to desired orientation // with velocity proportional to strength and angle AngularVelocity = axis * angle * strength * (float)Math.PI; // This ensures that we'll check this object on the next iteration ParentGroup.QueueForUpdateCheck(); } } catch (Exception ex) { m_log.Error("[Physics] " + ex); } } public Color4 GetTextColor() { Color color = Color; return new Color4(color.R, color.G, color.B, (byte)(0xFF - color.A)); } public void ResetOwnerChangeFlag() { List inv = Inventory.GetInventoryList(); foreach (UUID itemID in inv) { TaskInventoryItem item = Inventory.GetInventoryItem(itemID); item.OwnerChanged = false; Inventory.UpdateInventoryItem(item, false, false); } } /// /// Record an avatar sitting on this part. /// /// This is called for all the sitting avatars whether there is a sit target set or not. /// /// true if the avatar was not already recorded, false otherwise. /// /// protected internal bool AddSittingAvatar(ScenePresence sp) { lock (ParentGroup.m_sittingAvatars) { if (IsSitTargetSet && SitTargetAvatar == UUID.Zero) SitTargetAvatar = sp.UUID; if (m_sittingAvatars == null) m_sittingAvatars = new HashSet(); if (m_sittingAvatars.Add(sp)) { if(!ParentGroup.m_sittingAvatars.Contains(sp)) ParentGroup.m_sittingAvatars.Add(sp); return true; } return false; } } /// /// Remove an avatar recorded as sitting on this part. /// /// This applies to all sitting avatars whether there is a sit target set or not. /// /// true if the avatar was present and removed, false if it was not present. /// /// protected internal bool RemoveSittingAvatar(ScenePresence sp) { lock (ParentGroup.m_sittingAvatars) { if (SitTargetAvatar == sp.UUID) SitTargetAvatar = UUID.Zero; if (m_sittingAvatars == null) return false; if (m_sittingAvatars.Remove(sp)) { if (m_sittingAvatars.Count == 0) m_sittingAvatars = null; ParentGroup.m_sittingAvatars.Remove(sp); return true; } return false; } } /// /// Get a copy of the list of sitting avatars. /// /// This applies to all sitting avatars whether there is a sit target set or not. /// A hashset of the sitting avatars. Returns null if there are no sitting avatars. public HashSet GetSittingAvatars() { lock (ParentGroup.m_sittingAvatars) { if (m_sittingAvatars == null) return null; else return new HashSet(m_sittingAvatars); } } /// /// Gets the number of sitting avatars. /// /// This applies to all sitting avatars whether there is a sit target set or not. /// public int GetSittingAvatarsCount() { lock (ParentGroup.m_sittingAvatars) { if (m_sittingAvatars == null) return 0; else return m_sittingAvatars.Count; } } } }