/* * 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.Diagnostics; using System.Linq; using System.Threading; using System.Xml; using System.Xml.Serialization; using OpenMetaverse; using OpenMetaverse.Packets; using OpenSim.Framework; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Physics.Manager; using OpenSim.Region.Framework.Scenes.Serialization; namespace OpenSim.Region.Framework.Scenes { [Flags] public enum scriptEvents { None = 0, attach = 1, collision = 16, collision_end = 32, collision_start = 64, control = 128, dataserver = 256, email = 512, http_response = 1024, land_collision = 2048, land_collision_end = 4096, land_collision_start = 8192, at_target = 16384, at_rot_target = 16777216, listen = 32768, money = 65536, moving_end = 131072, moving_start = 262144, not_at_rot_target = 524288, not_at_target = 1048576, remote_data = 8388608, run_time_permissions = 268435456, state_entry = 1073741824, state_exit = 2, timer = 4, touch = 8, touch_end = 536870912, touch_start = 2097152, object_rez = 4194304 } struct scriptPosTarget { public Vector3 targetPos; public float tolerance; public uint handle; } struct scriptRotTarget { public Quaternion targetRot; public float tolerance; public uint handle; } public delegate void PrimCountTaintedDelegate(); /// /// A scene object group is conceptually an object in the scene. The object is constituted of SceneObjectParts /// (often known as prims), one of which is considered the root part. /// public partial class SceneObjectGroup : EntityBase, ISceneObject { // private PrimCountTaintedDelegate handlerPrimCountTainted = null; /// /// Signal whether the non-inventory attributes of any prims in the group have changed /// since the group's last persistent backup /// private bool m_hasGroupChanged = false; private long timeFirstChanged = 0; private long timeLastChanged = 0; private long m_maxPersistTime = 0; private long m_minPersistTime = 0; private Random m_rand; private bool m_suspendUpdates; private List m_linkedAvatars = new List(); public bool areUpdatesSuspended { get { return m_suspendUpdates; } set { m_suspendUpdates = value; if (!value) { QueueForUpdateCheck(); } } } public bool HasGroupChanged { set { if (value) { if (m_isBackedUp) { m_scene.SceneGraph.FireChangeBackup(this); } timeLastChanged = DateTime.Now.Ticks; if (!m_hasGroupChanged) timeFirstChanged = DateTime.Now.Ticks; if (m_rootPart != null && m_rootPart.UUID != null && m_scene != null) { if (m_rand == null) { byte[] val = new byte[16]; m_rootPart.UUID.ToBytes(val, 0); m_rand = new Random(BitConverter.ToInt32(val, 0)); } if (m_scene.GetRootAgentCount() == 0) { //If the region is empty, this change has been made by an automated process //and thus we delay the persist time by a random amount between 1.5 and 2.5. float factor = 1.5f + (float)(m_rand.NextDouble()); m_maxPersistTime = (long)((float)m_scene.m_persistAfter * factor); m_minPersistTime = (long)((float)m_scene.m_dontPersistBefore * factor); } else { //If the region is not empty, we want to obey the minimum and maximum persist times //but add a random factor so we stagger the object persistance a little m_maxPersistTime = (long)((float)m_scene.m_persistAfter * (1.0d - (m_rand.NextDouble() / 5.0d))); //Multiply by 1.0-1.5 m_minPersistTime = (long)((float)m_scene.m_dontPersistBefore * (1.0d + (m_rand.NextDouble() / 2.0d))); //Multiply by 0.8-1.0 } } } m_hasGroupChanged = value; // m_log.DebugFormat( // "[SCENE OBJECT GROUP]: HasGroupChanged set to {0} for {1} {2}", m_hasGroupChanged, Name, LocalId); } get { return m_hasGroupChanged; } } /// /// Has the group changed due to an unlink operation? We record this in order to optimize deletion, since /// an unlinked group currently has to be persisted to the database before we can perform an unlink operation. /// public bool HasGroupChangedDueToDelink { get; set; } private bool isTimeToPersist() { if (IsSelected || IsDeleted || IsAttachment) return false; if (!m_hasGroupChanged) return false; if (m_scene.ShuttingDown) return true; if (m_minPersistTime == 0 || m_maxPersistTime == 0) { m_maxPersistTime = m_scene.m_persistAfter; m_minPersistTime = m_scene.m_dontPersistBefore; } long currentTime = DateTime.Now.Ticks; if (timeLastChanged == 0) timeLastChanged = currentTime; if (timeFirstChanged == 0) timeFirstChanged = currentTime; if (currentTime - timeLastChanged > m_minPersistTime || currentTime - timeFirstChanged > m_maxPersistTime) return true; return false; } /// /// Is this scene object acting as an attachment? /// public bool IsAttachment { get; set; } /// /// The avatar to which this scene object is attached. /// /// /// If we're not attached to an avatar then this is UUID.Zero /// public UUID AttachedAvatar { get; set; } /// /// Attachment point of this scene object to an avatar. /// /// /// 0 if we're not attached to anything /// public uint AttachmentPoint { get { return m_rootPart.Shape.State; } set { IsAttachment = value != 0; m_rootPart.Shape.State = (byte)value; } } /// /// If this scene object has an attachment point then indicate whether there is a point where /// attachments are perceivable by avatars other than the avatar to which this object is attached. /// /// /// HUDs are not perceivable by other avatars. /// public bool HasPrivateAttachmentPoint { get { return AttachmentPoint >= (uint)OpenMetaverse.AttachmentPoint.HUDCenter2 && AttachmentPoint <= (uint)OpenMetaverse.AttachmentPoint.HUDBottomRight; } } public void ClearPartAttachmentData() { AttachmentPoint = 0; // Even though we don't use child part state parameters for attachments any more, we still need to set // these to zero since having them non-zero in rezzed scene objects will crash some clients. Even if // we store them correctly, scene objects that we receive from elsewhere might not. foreach (SceneObjectPart part in Parts) part.Shape.State = 0; } /// /// Is this scene object phantom? /// /// /// Updating must currently take place through UpdatePrimFlags() /// public bool IsPhantom { get { return (RootPart.Flags & PrimFlags.Phantom) != 0; } } /// /// Does this scene object use physics? /// /// /// Updating must currently take place through UpdatePrimFlags() /// public bool UsesPhysics { get { return (RootPart.Flags & PrimFlags.Physics) != 0; } } /// /// Is this scene object temporary? /// /// /// Updating must currently take place through UpdatePrimFlags() /// public bool IsTemporary { get { return (RootPart.Flags & PrimFlags.TemporaryOnRez) != 0; } } public bool IsVolumeDetect { get { return RootPart.VolumeDetectActive; } } private Vector3 lastPhysGroupPos; private Quaternion lastPhysGroupRot; private bool m_isBackedUp; protected MapAndArray m_parts = new MapAndArray(); protected ulong m_regionHandle; protected SceneObjectPart m_rootPart; // private Dictionary m_scriptEvents = new Dictionary(); private Dictionary m_targets = new Dictionary(); private Dictionary m_rotTargets = new Dictionary(); private bool m_scriptListens_atTarget; private bool m_scriptListens_notAtTarget; private bool m_scriptListens_atRotTarget; private bool m_scriptListens_notAtRotTarget; public bool m_dupeInProgress = false; internal Dictionary m_savedScriptState; #region Properties /// /// The name of an object grouping is always the same as its root part /// public override string Name { get { return RootPart.Name; } set { RootPart.Name = value; } } public string Description { get { return RootPart.Description; } set { RootPart.Description = value; } } /// /// Added because the Parcel code seems to use it /// but not sure a object should have this /// as what does it tell us? that some avatar has selected it (but not what Avatar/user) /// think really there should be a list (or whatever) in each scenepresence /// saying what prim(s) that user has selected. /// protected bool m_isSelected = false; /// /// Number of prims in this group /// public int PrimCount { get { return m_parts.Count; } } // protected Quaternion m_rotation = Quaternion.Identity; // // public virtual Quaternion Rotation // { // get { return m_rotation; } // set { // m_rotation = value; // } // } public Quaternion GroupRotation { get { return m_rootPart.RotationOffset; } } public Vector3 GroupScale { get { Vector3 minScale = new Vector3(Constants.RegionSize, Constants.RegionSize, Constants.RegionSize); Vector3 maxScale = Vector3.Zero; Vector3 finalScale = new Vector3(0.5f, 0.5f, 0.5f); SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) { SceneObjectPart part = parts[i]; Vector3 partscale = part.Scale; Vector3 partoffset = part.OffsetPosition; minScale.X = (partscale.X + partoffset.X < minScale.X) ? partscale.X + partoffset.X : minScale.X; minScale.Y = (partscale.Y + partoffset.Y < minScale.Y) ? partscale.Y + partoffset.Y : minScale.Y; minScale.Z = (partscale.Z + partoffset.Z < minScale.Z) ? partscale.Z + partoffset.Z : minScale.Z; maxScale.X = (partscale.X + partoffset.X > maxScale.X) ? partscale.X + partoffset.X : maxScale.X; maxScale.Y = (partscale.Y + partoffset.Y > maxScale.Y) ? partscale.Y + partoffset.Y : maxScale.Y; maxScale.Z = (partscale.Z + partoffset.Z > maxScale.Z) ? partscale.Z + partoffset.Z : maxScale.Z; } finalScale.X = (minScale.X > maxScale.X) ? minScale.X : maxScale.X; finalScale.Y = (minScale.Y > maxScale.Y) ? minScale.Y : maxScale.Y; finalScale.Z = (minScale.Z > maxScale.Z) ? minScale.Z : maxScale.Z; return finalScale; } } public UUID GroupID { get { return m_rootPart.GroupID; } set { m_rootPart.GroupID = value; } } public SceneObjectPart[] Parts { get { return m_parts.GetArray(); } } public bool ContainsPart(UUID partID) { return m_parts.ContainsKey(partID); } /// /// Does this group contain the given part? /// should be able to remove these methods once we have a entity index in scene /// /// /// public bool ContainsPart(uint localID) { SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) { if (parts[i].LocalId == localID) return true; } return false; } /// /// The root part of this scene object /// public SceneObjectPart RootPart { get { return m_rootPart; } } public ulong RegionHandle { get { return m_regionHandle; } set { m_regionHandle = value; SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) parts[i].RegionHandle = value; } } /// /// Check both the attachment property and the relevant properties of the underlying root part. /// /// /// This is necessary in some cases, particularly when a scene object has just crossed into a region and doesn't /// have the IsAttachment property yet checked. /// /// FIXME: However, this should be fixed so that this property /// propertly reflects the underlying status. /// /// public bool IsAttachmentCheckFull() { return (IsAttachment || (m_rootPart.Shape.PCode == 9 && m_rootPart.Shape.State != 0)); } private struct avtocrossInfo { public ScenePresence av; public uint ParentID; } /// /// The absolute position of this scene object in the scene /// public override Vector3 AbsolutePosition { get { return m_rootPart.GroupPosition; } set { Vector3 val = value; if (Scene != null) { // if ((Scene.TestBorderCross(val - Vector3.UnitX, Cardinals.E) || Scene.TestBorderCross(val + Vector3.UnitX, Cardinals.W) // || Scene.TestBorderCross(val - Vector3.UnitY, Cardinals.N) || Scene.TestBorderCross(val + Vector3.UnitY, Cardinals.S)) // && !IsAttachmentCheckFull() && (!Scene.LoadingPrims)) if ((Scene.TestBorderCross(val, Cardinals.E) || Scene.TestBorderCross(val, Cardinals.W) || Scene.TestBorderCross(val, Cardinals.N) || Scene.TestBorderCross(val, Cardinals.S)) && !IsAttachmentCheckFull() && (!Scene.LoadingPrims)) { IEntityTransferModule entityTransfer = m_scene.RequestModuleInterface(); uint x = 0; uint y = 0; string version = String.Empty; Vector3 newpos = Vector3.Zero; OpenSim.Services.Interfaces.GridRegion destination = null; bool canCross = true; foreach (ScenePresence av in m_linkedAvatars) { // We need to cross these agents. First, let's find // out if any of them can't cross for some reason. // We have to deny the crossing entirely if any // of them are banned. Alternatively, we could // unsit banned agents.... // We set the avatar position as being the object // position to get the region to send to if ((destination = entityTransfer.GetDestination(m_scene, av.UUID, val, out x, out y, out version, out newpos)) == null) { canCross = false; break; } m_log.DebugFormat("[SCENE OBJECT]: Avatar {0} needs to be crossed to {1}", av.Name, destination.RegionName); } if (canCross) { // We unparent the SP quietly so that it won't // be made to stand up List avsToCross = new List(); foreach (ScenePresence av in m_linkedAvatars) { avtocrossInfo avinfo = new avtocrossInfo(); SceneObjectPart parentPart = m_scene.GetSceneObjectPart(av.ParentID); if (parentPart != null) av.ParentUUID = parentPart.UUID; avinfo.av = av; avinfo.ParentID = av.ParentID; avsToCross.Add(avinfo); av.ParentID = 0; } // m_linkedAvatars.Clear(); m_scene.CrossPrimGroupIntoNewRegion(val, this, true); // Normalize if (val.X >= Constants.RegionSize) val.X -= Constants.RegionSize; if (val.Y >= Constants.RegionSize) val.Y -= Constants.RegionSize; if (val.X < 0) val.X += Constants.RegionSize; if (val.Y < 0) val.Y += Constants.RegionSize; // If it's deleted, crossing was successful if (IsDeleted) { // foreach (ScenePresence av in m_linkedAvatars) foreach (avtocrossInfo avinfo in avsToCross) { ScenePresence av = avinfo.av; if (!av.IsInTransit) // just in case... { m_log.DebugFormat("[SCENE OBJECT]: Crossing avatar {0} to {1}", av.Name, val); av.IsInTransit = true; CrossAgentToNewRegionDelegate d = entityTransfer.CrossAgentToNewRegionAsync; d.BeginInvoke(av, val, x, y, destination, av.Flying, version, CrossAgentToNewRegionCompleted, d); } else m_log.DebugFormat("[SCENE OBJECT]: Crossing avatar alreasy in transit {0} to {1}", av.Name, val); } avsToCross.Clear(); return; } else // cross failed, put avas back ?? { foreach (avtocrossInfo avinfo in avsToCross) { ScenePresence av = avinfo.av; av.ParentUUID = UUID.Zero; av.ParentID = avinfo.ParentID; // m_linkedAvatars.Add(av); } } avsToCross.Clear(); } else if (RootPart.PhysActor != null) { RootPart.PhysActor.CrossingFailure(); } Vector3 oldp = AbsolutePosition; val.X = Util.Clamp(oldp.X, 0.5f, (float)Constants.RegionSize - 0.5f); val.Y = Util.Clamp(oldp.Y, 0.5f, (float)Constants.RegionSize - 0.5f); val.Z = Util.Clamp(oldp.Z, 0.5f, 4096.0f); } } /* don't see the need but worse don't see where is restored to false if things stay in foreach (SceneObjectPart part in m_parts.GetArray()) { part.IgnoreUndoUpdate = true; } */ if (RootPart.GetStatusSandbox()) { if (Util.GetDistanceTo(RootPart.StatusSandboxPos, value) > 10) { RootPart.ScriptSetPhysicsStatus(false); if (Scene != null) Scene.SimChat(Utils.StringToBytes("Hit Sandbox Limit"), ChatTypeEnum.DebugChannel, 0x7FFFFFFF, RootPart.AbsolutePosition, Name, UUID, false); return; } } SceneObjectPart[] parts = m_parts.GetArray(); bool triggerScriptEvent = m_rootPart.GroupPosition != val; if (m_dupeInProgress) triggerScriptEvent = false; foreach (SceneObjectPart part in parts) { part.GroupPosition = val; if (triggerScriptEvent) part.TriggerScriptChangedEvent(Changed.POSITION); } if (!m_dupeInProgress) { foreach (ScenePresence av in m_linkedAvatars) { SceneObjectPart p = m_scene.GetSceneObjectPart(av.ParentID); if (p != null && m_parts.TryGetValue(p.UUID, out p)) { Vector3 offset = p.GetWorldPosition() - av.ParentPosition; av.AbsolutePosition += offset; av.ParentPosition = p.GetWorldPosition(); //ParentPosition gets cleared by AbsolutePosition av.SendAvatarDataToAllAgents(); } } } //if (m_rootPart.PhysActor != null) //{ //m_rootPart.PhysActor.Position = //new PhysicsVector(m_rootPart.GroupPosition.X, m_rootPart.GroupPosition.Y, //m_rootPart.GroupPosition.Z); //m_scene.PhysicsScene.AddPhysicsActorTaint(m_rootPart.PhysActor); //} if (Scene != null) Scene.EventManager.TriggerParcelPrimCountTainted(); } } public override Vector3 Velocity { get { return RootPart.Velocity; } set { RootPart.Velocity = value; } } private void CrossAgentToNewRegionCompleted(IAsyncResult iar) { CrossAgentToNewRegionDelegate icon = (CrossAgentToNewRegionDelegate)iar.AsyncState; ScenePresence agent = icon.EndInvoke(iar); //// If the cross was successful, this agent is a child agent if (agent.IsChildAgent) { if (agent.ParentUUID != UUID.Zero) { agent.ParentPart = null; agent.ParentPosition = Vector3.Zero; // agent.ParentUUID = UUID.Zero; } } agent.ParentUUID = UUID.Zero; // agent.Reset(); // else // Not successful // agent.RestoreInCurrentScene(); // In any case agent.IsInTransit = false; m_log.DebugFormat("[SCENE OBJECT]: Crossing agent {0} {1} completed.", agent.Firstname, agent.Lastname); } public override uint LocalId { get { return m_rootPart.LocalId; } set { m_rootPart.LocalId = value; } } public override UUID UUID { get { return m_rootPart.UUID; } set { lock (m_parts.SyncRoot) { m_parts.Remove(m_rootPart.UUID); m_rootPart.UUID = value; m_parts.Add(value, m_rootPart); } } } public UUID LastOwnerID { get { return m_rootPart.LastOwnerID; } set { m_rootPart.LastOwnerID = value; } } public UUID OwnerID { get { return m_rootPart.OwnerID; } set { m_rootPart.OwnerID = value; } } public float Damage { get { return m_rootPart.Damage; } set { m_rootPart.Damage = value; } } public Color Color { get { return m_rootPart.Color; } set { m_rootPart.Color = value; } } public string Text { get { string returnstr = m_rootPart.Text; if (returnstr.Length > 255) { returnstr = returnstr.Substring(0, 255); } return returnstr; } set { m_rootPart.Text = value; } } protected virtual bool InSceneBackup { get { return true; } } public bool IsSelected { get { return m_isSelected; } set { m_isSelected = value; // Tell physics engine that group is selected // this is not right // but ode engines should only really need to know about root part // so they can put entire object simulation on hold and not colliding // keep as was for now PhysicsActor pa = m_rootPart.PhysActor; if (pa != null) { pa.Selected = value; // Pass it on to the children. SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) { SceneObjectPart child = parts[i]; PhysicsActor childPa = child.PhysActor; if (childPa != null) childPa.Selected = value; } } if (RootPart.KeyframeMotion != null) RootPart.KeyframeMotion.Selected = value; } } public void PartSelectChanged(bool partSelect) { // any part selected makes group selected if (m_isSelected == partSelect) return; if (partSelect) { IsSelected = partSelect; // if (!IsAttachment) // ScheduleGroupForFullUpdate(); } else { // bad bad bad 2 heavy for large linksets // since viewer does send lot of (un)selects // this needs to be replaced by a specific list or count ? // but that will require extra code in several places SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) { SceneObjectPart part = parts[i]; if (part.IsSelected) return; } IsSelected = partSelect; if (!IsAttachment) { ScheduleGroupForFullUpdate(); } } } 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; } } /// /// The UUID for the region this object is in. /// public UUID RegionUUID { get { if (m_scene != null) { return m_scene.RegionInfo.RegionID; } return UUID.Zero; } } /// /// The item ID that this object was rezzed from, if applicable. /// /// /// If not applicable will be UUID.Zero /// public UUID FromItemID { get; set; } /// /// The folder ID that this object was rezzed from, if applicable. /// /// /// If not applicable will be UUID.Zero /// public UUID FromFolderID { get; set; } #endregion // ~SceneObjectGroup() // { // //m_log.DebugFormat("[SCENE OBJECT GROUP]: Destructor called for {0}, local id {1}", Name, LocalId); // Console.WriteLine("Destructor called for {0}, local id {1}", Name, LocalId); // } #region Constructors /// /// Constructor /// public SceneObjectGroup() { } /// /// This constructor creates a SceneObjectGroup using a pre-existing SceneObjectPart. /// The original SceneObjectPart will be used rather than a copy, preserving /// its existing localID and UUID. /// public SceneObjectGroup(SceneObjectPart part) { SetRootPart(part); } /// /// Constructor. This object is added to the scene later via AttachToScene() /// public SceneObjectGroup(UUID ownerID, Vector3 pos, Quaternion rot, PrimitiveBaseShape shape) { SetRootPart(new SceneObjectPart(ownerID, shape, pos, rot, Vector3.Zero)); } /// /// Constructor. /// public SceneObjectGroup(UUID ownerID, Vector3 pos, PrimitiveBaseShape shape) : this(ownerID, pos, Quaternion.Identity, shape) { } public void LoadScriptState(XmlDocument doc) { XmlNodeList nodes = doc.GetElementsByTagName("SavedScriptState"); if (nodes.Count > 0) { if (m_savedScriptState == null) m_savedScriptState = new Dictionary(); foreach (XmlNode node in nodes) { if (node.Attributes["UUID"] != null) { UUID itemid = new UUID(node.Attributes["UUID"].Value); if (itemid != UUID.Zero) m_savedScriptState[itemid] = node.InnerXml; } } } } /// /// Hooks this object up to the backup event so that it is persisted to the database when the update thread executes. /// public virtual void AttachToBackup() { if (IsAttachment) return; m_scene.SceneGraph.FireAttachToBackup(this); if (InSceneBackup) { //m_log.DebugFormat( // "[SCENE OBJECT GROUP]: Attaching object {0} {1} to scene presistence sweep", Name, UUID); if (!m_isBackedUp) m_scene.EventManager.OnBackup += ProcessBackup; m_isBackedUp = true; } } /// /// Attach this object to a scene. It will also now appear to agents. /// /// public void AttachToScene(Scene scene) { m_scene = scene; RegionHandle = m_scene.RegionInfo.RegionHandle; if (m_rootPart.Shape.PCode != 9 || m_rootPart.Shape.State == 0) m_rootPart.ParentID = 0; if (m_rootPart.LocalId == 0) m_rootPart.LocalId = m_scene.AllocateLocalId(); SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) { SceneObjectPart part = parts[i]; if (Object.ReferenceEquals(part, m_rootPart)) continue; if (part.LocalId == 0) part.LocalId = m_scene.AllocateLocalId(); part.ParentID = m_rootPart.LocalId; //m_log.DebugFormat("[SCENE]: Given local id {0} to part {1}, linknum {2}, parent {3} {4}", part.LocalId, part.UUID, part.LinkNum, part.ParentID, part.ParentUUID); } ApplyPhysics(); if (RootPart.PhysActor != null) RootPart.Force = RootPart.Force; if (RootPart.PhysActor != null) RootPart.Torque = RootPart.Torque; if (RootPart.PhysActor != null) RootPart.Buoyancy = RootPart.Buoyancy; // Don't trigger the update here - otherwise some client issues occur when multiple updates are scheduled // for the same object with very different properties. The caller must schedule the update. //ScheduleGroupForFullUpdate(); } public EntityIntersection TestIntersection(Ray hRay, bool frontFacesOnly, bool faceCenters) { // We got a request from the inner_scene to raytrace along the Ray hRay // We're going to check all of the prim in this group for intersection with the ray // If we get a result, we're going to find the closest result to the origin of the ray // and send back the intersection information back to the innerscene. EntityIntersection result = new EntityIntersection(); SceneObjectPart[] parts = m_parts.GetArray(); // Find closest hit here float idist = float.MaxValue; for (int i = 0; i < parts.Length; i++) { SceneObjectPart part = parts[i]; // Temporary commented to stop compiler warning //Vector3 partPosition = // new Vector3(part.AbsolutePosition.X, part.AbsolutePosition.Y, part.AbsolutePosition.Z); Quaternion parentrotation = GroupRotation; // Telling the prim to raytrace. //EntityIntersection inter = part.TestIntersection(hRay, parentrotation); EntityIntersection inter = part.TestIntersectionOBB(hRay, parentrotation, frontFacesOnly, faceCenters); if (inter.HitTF) { // We need to find the closest prim to return to the testcaller along the ray if (inter.distance < idist) { result.HitTF = true; result.ipoint = inter.ipoint; result.obj = part; result.normal = inter.normal; result.distance = inter.distance; idist = inter.distance; } } } return result; } /// /// Gets a vector representing the size of the bounding box containing all the prims in the group /// Treats all prims as rectangular, so no shape (cut etc) is taken into account /// offsetHeight is the offset in the Z axis from the centre of the bounding box to the centre of the root prim /// /// public void GetAxisAlignedBoundingBoxRaw(out float minX, out float maxX, out float minY, out float maxY, out float minZ, out float maxZ) { maxX = float.MinValue; maxY = float.MinValue; maxZ = float.MinValue; minX = float.MaxValue; minY = float.MaxValue; minZ = float.MaxValue; SceneObjectPart[] parts = m_parts.GetArray(); foreach (SceneObjectPart part in parts) { Vector3 worldPos = part.GetWorldPosition(); Vector3 offset = worldPos - AbsolutePosition; Quaternion worldRot; if (part.ParentID == 0) { worldRot = part.RotationOffset; } else { worldRot = part.GetWorldRotation(); } Vector3 frontTopLeft; Vector3 frontTopRight; Vector3 frontBottomLeft; Vector3 frontBottomRight; Vector3 backTopLeft; Vector3 backTopRight; Vector3 backBottomLeft; Vector3 backBottomRight; // Vector3[] corners = new Vector3[8]; Vector3 orig = Vector3.Zero; frontTopLeft.X = orig.X - (part.Scale.X / 2); frontTopLeft.Y = orig.Y - (part.Scale.Y / 2); frontTopLeft.Z = orig.Z + (part.Scale.Z / 2); frontTopRight.X = orig.X - (part.Scale.X / 2); frontTopRight.Y = orig.Y + (part.Scale.Y / 2); frontTopRight.Z = orig.Z + (part.Scale.Z / 2); frontBottomLeft.X = orig.X - (part.Scale.X / 2); frontBottomLeft.Y = orig.Y - (part.Scale.Y / 2); frontBottomLeft.Z = orig.Z - (part.Scale.Z / 2); frontBottomRight.X = orig.X - (part.Scale.X / 2); frontBottomRight.Y = orig.Y + (part.Scale.Y / 2); frontBottomRight.Z = orig.Z - (part.Scale.Z / 2); backTopLeft.X = orig.X + (part.Scale.X / 2); backTopLeft.Y = orig.Y - (part.Scale.Y / 2); backTopLeft.Z = orig.Z + (part.Scale.Z / 2); backTopRight.X = orig.X + (part.Scale.X / 2); backTopRight.Y = orig.Y + (part.Scale.Y / 2); backTopRight.Z = orig.Z + (part.Scale.Z / 2); backBottomLeft.X = orig.X + (part.Scale.X / 2); backBottomLeft.Y = orig.Y - (part.Scale.Y / 2); backBottomLeft.Z = orig.Z - (part.Scale.Z / 2); backBottomRight.X = orig.X + (part.Scale.X / 2); backBottomRight.Y = orig.Y + (part.Scale.Y / 2); backBottomRight.Z = orig.Z - (part.Scale.Z / 2); //m_log.InfoFormat("pre corner 1 is {0} {1} {2}", frontTopLeft.X, frontTopLeft.Y, frontTopLeft.Z); //m_log.InfoFormat("pre corner 2 is {0} {1} {2}", frontTopRight.X, frontTopRight.Y, frontTopRight.Z); //m_log.InfoFormat("pre corner 3 is {0} {1} {2}", frontBottomRight.X, frontBottomRight.Y, frontBottomRight.Z); //m_log.InfoFormat("pre corner 4 is {0} {1} {2}", frontBottomLeft.X, frontBottomLeft.Y, frontBottomLeft.Z); //m_log.InfoFormat("pre corner 5 is {0} {1} {2}", backTopLeft.X, backTopLeft.Y, backTopLeft.Z); //m_log.InfoFormat("pre corner 6 is {0} {1} {2}", backTopRight.X, backTopRight.Y, backTopRight.Z); //m_log.InfoFormat("pre corner 7 is {0} {1} {2}", backBottomRight.X, backBottomRight.Y, backBottomRight.Z); //m_log.InfoFormat("pre corner 8 is {0} {1} {2}", backBottomLeft.X, backBottomLeft.Y, backBottomLeft.Z); //for (int i = 0; i < 8; i++) //{ // corners[i] = corners[i] * worldRot; // corners[i] += offset; // if (corners[i].X > maxX) // maxX = corners[i].X; // if (corners[i].X < minX) // minX = corners[i].X; // if (corners[i].Y > maxY) // maxY = corners[i].Y; // if (corners[i].Y < minY) // minY = corners[i].Y; // if (corners[i].Z > maxZ) // maxZ = corners[i].Y; // if (corners[i].Z < minZ) // minZ = corners[i].Z; //} frontTopLeft = frontTopLeft * worldRot; frontTopRight = frontTopRight * worldRot; frontBottomLeft = frontBottomLeft * worldRot; frontBottomRight = frontBottomRight * worldRot; backBottomLeft = backBottomLeft * worldRot; backBottomRight = backBottomRight * worldRot; backTopLeft = backTopLeft * worldRot; backTopRight = backTopRight * worldRot; frontTopLeft += offset; frontTopRight += offset; frontBottomLeft += offset; frontBottomRight += offset; backBottomLeft += offset; backBottomRight += offset; backTopLeft += offset; backTopRight += offset; //m_log.InfoFormat("corner 1 is {0} {1} {2}", frontTopLeft.X, frontTopLeft.Y, frontTopLeft.Z); //m_log.InfoFormat("corner 2 is {0} {1} {2}", frontTopRight.X, frontTopRight.Y, frontTopRight.Z); //m_log.InfoFormat("corner 3 is {0} {1} {2}", frontBottomRight.X, frontBottomRight.Y, frontBottomRight.Z); //m_log.InfoFormat("corner 4 is {0} {1} {2}", frontBottomLeft.X, frontBottomLeft.Y, frontBottomLeft.Z); //m_log.InfoFormat("corner 5 is {0} {1} {2}", backTopLeft.X, backTopLeft.Y, backTopLeft.Z); //m_log.InfoFormat("corner 6 is {0} {1} {2}", backTopRight.X, backTopRight.Y, backTopRight.Z); //m_log.InfoFormat("corner 7 is {0} {1} {2}", backBottomRight.X, backBottomRight.Y, backBottomRight.Z); //m_log.InfoFormat("corner 8 is {0} {1} {2}", backBottomLeft.X, backBottomLeft.Y, backBottomLeft.Z); if (frontTopRight.X > maxX) maxX = frontTopRight.X; if (frontTopLeft.X > maxX) maxX = frontTopLeft.X; if (frontBottomRight.X > maxX) maxX = frontBottomRight.X; if (frontBottomLeft.X > maxX) maxX = frontBottomLeft.X; if (backTopRight.X > maxX) maxX = backTopRight.X; if (backTopLeft.X > maxX) maxX = backTopLeft.X; if (backBottomRight.X > maxX) maxX = backBottomRight.X; if (backBottomLeft.X > maxX) maxX = backBottomLeft.X; if (frontTopRight.X < minX) minX = frontTopRight.X; if (frontTopLeft.X < minX) minX = frontTopLeft.X; if (frontBottomRight.X < minX) minX = frontBottomRight.X; if (frontBottomLeft.X < minX) minX = frontBottomLeft.X; if (backTopRight.X < minX) minX = backTopRight.X; if (backTopLeft.X < minX) minX = backTopLeft.X; if (backBottomRight.X < minX) minX = backBottomRight.X; if (backBottomLeft.X < minX) minX = backBottomLeft.X; // if (frontTopRight.Y > maxY) maxY = frontTopRight.Y; if (frontTopLeft.Y > maxY) maxY = frontTopLeft.Y; if (frontBottomRight.Y > maxY) maxY = frontBottomRight.Y; if (frontBottomLeft.Y > maxY) maxY = frontBottomLeft.Y; if (backTopRight.Y > maxY) maxY = backTopRight.Y; if (backTopLeft.Y > maxY) maxY = backTopLeft.Y; if (backBottomRight.Y > maxY) maxY = backBottomRight.Y; if (backBottomLeft.Y > maxY) maxY = backBottomLeft.Y; if (frontTopRight.Y < minY) minY = frontTopRight.Y; if (frontTopLeft.Y < minY) minY = frontTopLeft.Y; if (frontBottomRight.Y < minY) minY = frontBottomRight.Y; if (frontBottomLeft.Y < minY) minY = frontBottomLeft.Y; if (backTopRight.Y < minY) minY = backTopRight.Y; if (backTopLeft.Y < minY) minY = backTopLeft.Y; if (backBottomRight.Y < minY) minY = backBottomRight.Y; if (backBottomLeft.Y < minY) minY = backBottomLeft.Y; // if (frontTopRight.Z > maxZ) maxZ = frontTopRight.Z; if (frontTopLeft.Z > maxZ) maxZ = frontTopLeft.Z; if (frontBottomRight.Z > maxZ) maxZ = frontBottomRight.Z; if (frontBottomLeft.Z > maxZ) maxZ = frontBottomLeft.Z; if (backTopRight.Z > maxZ) maxZ = backTopRight.Z; if (backTopLeft.Z > maxZ) maxZ = backTopLeft.Z; if (backBottomRight.Z > maxZ) maxZ = backBottomRight.Z; if (backBottomLeft.Z > maxZ) maxZ = backBottomLeft.Z; if (frontTopRight.Z < minZ) minZ = frontTopRight.Z; if (frontTopLeft.Z < minZ) minZ = frontTopLeft.Z; if (frontBottomRight.Z < minZ) minZ = frontBottomRight.Z; if (frontBottomLeft.Z < minZ) minZ = frontBottomLeft.Z; if (backTopRight.Z < minZ) minZ = backTopRight.Z; if (backTopLeft.Z < minZ) minZ = backTopLeft.Z; if (backBottomRight.Z < minZ) minZ = backBottomRight.Z; if (backBottomLeft.Z < minZ) minZ = backBottomLeft.Z; } } public Vector3 GetAxisAlignedBoundingBox(out float offsetHeight) { float minX; float maxX; float minY; float maxY; float minZ; float maxZ; GetAxisAlignedBoundingBoxRaw(out minX, out maxX, out minY, out maxY, out minZ, out maxZ); Vector3 boundingBox = new Vector3(maxX - minX, maxY - minY, maxZ - minZ); offsetHeight = 0; float lower = (minZ * -1); if (lower > maxZ) { offsetHeight = lower - (boundingBox.Z / 2); } else if (maxZ > lower) { offsetHeight = maxZ - (boundingBox.Z / 2); offsetHeight *= -1; } // m_log.InfoFormat("BoundingBox is {0} , {1} , {2} ", boundingBox.X, boundingBox.Y, boundingBox.Z); return boundingBox; } #endregion public void GetResourcesCosts(SceneObjectPart apart, out float linksetResCost, out float linksetPhysCost, out float partCost, out float partPhysCost) { // this information may need to be cached float cost; float tmpcost; bool ComplexCost = false; SceneObjectPart p; SceneObjectPart[] parts; lock (m_parts) { parts = m_parts.GetArray(); } int nparts = parts.Length; for (int i = 0; i < nparts; i++) { p = parts[i]; if (p.UsesComplexCost) { ComplexCost = true; break; } } if (ComplexCost) { linksetResCost = 0; linksetPhysCost = 0; partCost = 0; partPhysCost = 0; for (int i = 0; i < nparts; i++) { p = parts[i]; cost = p.StreamingCost; tmpcost = p.SimulationCost; if (tmpcost > cost) cost = tmpcost; tmpcost = p.PhysicsCost; if (tmpcost > cost) cost = tmpcost; linksetPhysCost += tmpcost; linksetResCost += cost; if (p == apart) { partCost = cost; partPhysCost = tmpcost; } } } else { partPhysCost = 1.0f; partCost = 1.0f; linksetResCost = (float)nparts; linksetPhysCost = linksetResCost; } } public void GetSelectedCosts(out float PhysCost, out float StreamCost, out float SimulCost) { SceneObjectPart p; SceneObjectPart[] parts; lock (m_parts) { parts = m_parts.GetArray(); } int nparts = parts.Length; PhysCost = 0; StreamCost = 0; SimulCost = 0; for (int i = 0; i < nparts; i++) { p = parts[i]; StreamCost += p.StreamingCost; SimulCost += p.SimulationCost; PhysCost += p.PhysicsCost; } } public void SaveScriptedState(XmlTextWriter writer) { SaveScriptedState(writer, false); } public void SaveScriptedState(XmlTextWriter writer, bool oldIDs) { XmlDocument doc = new XmlDocument(); Dictionary states = new Dictionary(); SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) { Dictionary pstates = parts[i].Inventory.GetScriptStates(oldIDs); foreach (KeyValuePair kvp in pstates) states[kvp.Key] = kvp.Value; } if (states.Count > 0) { // Now generate the necessary XML wrappings writer.WriteStartElement(String.Empty, "GroupScriptStates", String.Empty); foreach (UUID itemid in states.Keys) { doc.LoadXml(states[itemid]); writer.WriteStartElement(String.Empty, "SavedScriptState", String.Empty); writer.WriteAttributeString(String.Empty, "UUID", String.Empty, itemid.ToString()); writer.WriteRaw(doc.DocumentElement.OuterXml); // Writes ScriptState element writer.WriteEndElement(); // End of SavedScriptState } writer.WriteEndElement(); // End of GroupScriptStates } } /// /// Add the avatar to this linkset (avatar is sat). /// /// public void AddAvatar(UUID agentID) { ScenePresence presence; if (m_scene.TryGetScenePresence(agentID, out presence)) { if (!m_linkedAvatars.Contains(presence)) { m_linkedAvatars.Add(presence); } } } /// /// Delete the avatar from this linkset (avatar is unsat). /// /// public void DeleteAvatar(UUID agentID) { ScenePresence presence; if (m_scene.TryGetScenePresence(agentID, out presence)) { if (m_linkedAvatars.Contains(presence)) { m_linkedAvatars.Remove(presence); } } } /// /// Returns the list of linked presences (avatars sat on this group) /// /// public List GetLinkedAvatars() { return m_linkedAvatars; } /// /// Attach this scene object to the given avatar. /// /// /// /// private void AttachToAgent( ScenePresence avatar, SceneObjectGroup so, uint attachmentpoint, Vector3 attachOffset, bool silent) { if (avatar != null) { // don't attach attachments to child agents if (avatar.IsChildAgent) return; // Remove from database and parcel prim count m_scene.DeleteFromStorage(so.UUID); m_scene.EventManager.TriggerParcelPrimCountTainted(); so.AttachedAvatar = avatar.UUID; if (so.RootPart.PhysActor != null) { m_scene.PhysicsScene.RemovePrim(so.RootPart.PhysActor); so.RootPart.PhysActor = null; } so.AbsolutePosition = attachOffset; so.RootPart.AttachedPos = attachOffset; so.IsAttachment = true; so.RootPart.SetParentLocalId(avatar.LocalId); so.AttachmentPoint = attachmentpoint; avatar.AddAttachment(this); if (!silent) { // Killing it here will cause the client to deselect it // It then reappears on the avatar, deselected // through the full update below // if (IsSelected) { m_scene.SendKillObject(new List { m_rootPart.LocalId }); } IsSelected = false; // fudge.... ScheduleGroupForFullUpdate(); } } else { m_log.WarnFormat( "[SOG]: Tried to add attachment {0} to avatar with UUID {1} in region {2} but the avatar is not present", UUID, avatar.ControllingClient.AgentId, Scene.RegionInfo.RegionName); } } public byte GetAttachmentPoint() { return m_rootPart.Shape.State; } public void DetachToGround() { ScenePresence avatar = m_scene.GetScenePresence(AttachedAvatar); if (avatar == null) return; avatar.RemoveAttachment(this); Vector3 detachedpos = new Vector3(127f,127f,127f); if (avatar == null) return; detachedpos = avatar.AbsolutePosition; FromItemID = UUID.Zero; AbsolutePosition = detachedpos; AttachedAvatar = UUID.Zero; //SceneObjectPart[] parts = m_parts.GetArray(); //for (int i = 0; i < parts.Length; i++) // parts[i].AttachedAvatar = UUID.Zero; m_rootPart.SetParentLocalId(0); AttachmentPoint = (byte)0; // must check if buildind should be true or false here m_rootPart.ApplyPhysics(m_rootPart.GetEffectiveObjectFlags(), m_rootPart.VolumeDetectActive,false); HasGroupChanged = true; RootPart.Rezzed = DateTime.Now; RootPart.RemFlag(PrimFlags.TemporaryOnRez); AttachToBackup(); m_scene.EventManager.TriggerParcelPrimCountTainted(); m_rootPart.ScheduleFullUpdate(); m_rootPart.ClearUndoState(); } public void DetachToInventoryPrep() { ScenePresence avatar = m_scene.GetScenePresence(AttachedAvatar); //Vector3 detachedpos = new Vector3(127f, 127f, 127f); if (avatar != null) { //detachedpos = avatar.AbsolutePosition; avatar.RemoveAttachment(this); } AttachedAvatar = UUID.Zero; /*SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) parts[i].AttachedAvatar = UUID.Zero;*/ m_rootPart.SetParentLocalId(0); //m_rootPart.SetAttachmentPoint((byte)0); IsAttachment = false; AbsolutePosition = m_rootPart.AttachedPos; //m_rootPart.ApplyPhysics(m_rootPart.GetEffectiveObjectFlags(), m_scene.m_physicalPrim); //AttachToBackup(); //m_rootPart.ScheduleFullUpdate(); } /// /// /// /// private void SetPartAsNonRoot(SceneObjectPart part) { part.ParentID = m_rootPart.LocalId; part.ClearUndoState(); } public ushort GetTimeDilation() { return Utils.FloatToUInt16(m_scene.TimeDilation, 0.0f, 1.0f); } /// /// Added as a way for the storage provider to reset the scene, /// most likely a better way to do this sort of thing but for now... /// /// public void SetScene(Scene scene) { m_scene = scene; } /// /// Set a part to act as the root part for this scene object /// /// public void SetRootPart(SceneObjectPart part) { if (part == null) throw new ArgumentNullException("Cannot give SceneObjectGroup a null root SceneObjectPart"); part.SetParent(this); m_rootPart = part; if (!IsAttachment) part.ParentID = 0; part.LinkNum = 0; m_parts.Add(m_rootPart.UUID, m_rootPart); } /// /// Add a new part to this scene object. The part must already be correctly configured. /// /// public void AddPart(SceneObjectPart part) { part.SetParent(this); m_parts.Add(part.UUID, part); part.LinkNum = m_parts.Count; if (part.LinkNum == 2) RootPart.LinkNum = 1; } /// /// Make sure that every non root part has the proper parent root part local id /// private void UpdateParentIDs() { SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) { SceneObjectPart part = parts[i]; if (part.UUID != m_rootPart.UUID) part.ParentID = m_rootPart.LocalId; } } public void RegenerateFullIDs() { SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) parts[i].UUID = UUID.Random(); } // helper provided for parts. public int GetSceneMaxUndo() { if (m_scene != null) return m_scene.MaxUndoCount; return 5; } // justincc: I don't believe this hack is needed any longer, especially since the physics // parts of set AbsolutePosition were already commented out. By changing HasGroupChanged to false // this method was preventing proper reload of scene objects. // dahlia: I had to uncomment it, without it meshing was failing on some prims and objects // at region startup // teravus: After this was removed from the linking algorithm, Linked prims no longer collided // properly when non-physical if they havn't been moved. This breaks ALL builds. // see: http://opensimulator.org/mantis/view.php?id=3108 // Here's the deal, this is ABSOLUTELY CRITICAL so the physics scene gets the update about the // position of linkset prims. IF YOU CHANGE THIS, YOU MUST TEST colliding with just linked and // unmoved prims! As soon as you move a Prim/group, it will collide properly because Absolute // Position has been set! public void ResetChildPrimPhysicsPositions() { AbsolutePosition = AbsolutePosition; // could someone in the know please explain how this works? // teravus: AbsolutePosition is NOT a normal property! // the code in the getter of AbsolutePosition is significantly different then the code in the setter! // jhurliman: Then why is it a property instead of two methods? } public UUID GetPartsFullID(uint localID) { SceneObjectPart part = GetPart(localID); if (part != null) { return part.UUID; } return UUID.Zero; } public void ObjectGrabHandler(uint localId, Vector3 offsetPos, IClientAPI remoteClient) { if (m_rootPart.LocalId == localId) { OnGrabGroup(offsetPos, remoteClient); } else { SceneObjectPart part = GetPart(localId); OnGrabPart(part, offsetPos, remoteClient); } } public virtual void OnGrabPart(SceneObjectPart part, Vector3 offsetPos, IClientAPI remoteClient) { // m_log.DebugFormat( // "[SCENE OBJECT GROUP]: Processing OnGrabPart for {0} on {1} {2}, offsetPos {3}", // remoteClient.Name, part.Name, part.LocalId, offsetPos); // part.StoreUndoState(); part.OnGrab(offsetPos, remoteClient); } public virtual void OnGrabGroup(Vector3 offsetPos, IClientAPI remoteClient) { m_scene.EventManager.TriggerGroupGrab(UUID, offsetPos, remoteClient.AgentId); } /// /// Delete this group from its scene. /// /// /// This only handles the in-world consequences of deletion (e.g. any avatars sitting on it are forcibly stood /// up and all avatars receive notification of its removal. Removal of the scene object from database backup /// must be handled by the caller. /// /// If true then deletion is not broadcast to clients public void DeleteGroupFromScene(bool silent) { // We need to keep track of this state in case this group is still queued for backup. IsDeleted = true; DetachFromBackup(); SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) { SceneObjectPart part = parts[i]; Scene.ForEachRootScenePresence(delegate(ScenePresence avatar) { if (avatar.ParentID == LocalId) avatar.StandUp(); if (!silent) { part.ClearUpdateSchedule(); if (part == m_rootPart) { if (!IsAttachment || AttachedAvatar == avatar.ControllingClient.AgentId || !HasPrivateAttachmentPoint) avatar.ControllingClient.SendKillObject(m_regionHandle, new List { part.LocalId }); } } }); } } public void AddScriptLPS(int count) { m_scene.SceneGraph.AddToScriptLPS(count); } public void AddActiveScriptCount(int count) { SceneGraph d = m_scene.SceneGraph; d.AddActiveScripts(count); } public void aggregateScriptEvents() { PrimFlags objectflagupdate = (PrimFlags)RootPart.GetEffectiveObjectFlags(); scriptEvents aggregateScriptEvents = 0; SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) { SceneObjectPart part = parts[i]; if (part == null) continue; if (part != RootPart) part.Flags = objectflagupdate; aggregateScriptEvents |= part.AggregateScriptEvents; } m_scriptListens_atTarget = ((aggregateScriptEvents & scriptEvents.at_target) != 0); m_scriptListens_notAtTarget = ((aggregateScriptEvents & scriptEvents.not_at_target) != 0); if (!m_scriptListens_atTarget && !m_scriptListens_notAtTarget) { lock (m_targets) m_targets.Clear(); m_scene.RemoveGroupTarget(this); } m_scriptListens_atRotTarget = ((aggregateScriptEvents & scriptEvents.at_rot_target) != 0); m_scriptListens_notAtRotTarget = ((aggregateScriptEvents & scriptEvents.not_at_rot_target) != 0); if (!m_scriptListens_atRotTarget && !m_scriptListens_notAtRotTarget) { lock (m_rotTargets) m_rotTargets.Clear(); m_scene.RemoveGroupTarget(this); } ScheduleGroupForFullUpdate(); } public void SetText(string text, Vector3 color, double alpha) { Color = Color.FromArgb(0xff - (int) (alpha * 0xff), (int) (color.X * 0xff), (int) (color.Y * 0xff), (int) (color.Z * 0xff)); Text = text; HasGroupChanged = true; m_rootPart.ScheduleFullUpdate(); } /// /// Apply physics to this group /// public void ApplyPhysics() { SceneObjectPart[] parts = m_parts.GetArray(); if (parts.Length > 1) { ResetChildPrimPhysicsPositions(); // Apply physics to the root prim m_rootPart.ApplyPhysics(m_rootPart.GetEffectiveObjectFlags(), m_rootPart.VolumeDetectActive, true); for (int i = 0; i < parts.Length; i++) { SceneObjectPart part = parts[i]; if (part.LocalId != m_rootPart.LocalId) part.ApplyPhysics(m_rootPart.GetEffectiveObjectFlags(), part.VolumeDetectActive, true); } // Hack to get the physics scene geometries in the right spot // ResetChildPrimPhysicsPositions(); if (m_rootPart.PhysActor != null) { m_rootPart.PhysActor.Building = false; } } else { // Apply physics to the root prim m_rootPart.ApplyPhysics(m_rootPart.GetEffectiveObjectFlags(), m_rootPart.VolumeDetectActive, false); } } public void SetOwnerId(UUID userId) { ForEachPart(delegate(SceneObjectPart part) { part.OwnerID = userId; }); } public void ForEachPart(Action whatToDo) { SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) whatToDo(parts[i]); } #region Events /// /// Processes backup. /// /// public virtual void ProcessBackup(ISimulationDataService datastore, bool forcedBackup) { if (!m_isBackedUp) { // m_log.DebugFormat( // "[WATER WARS]: Ignoring backup of {0} {1} since object is not marked to be backed up", Name, UUID); return; } if (IsDeleted || UUID == UUID.Zero) { // m_log.DebugFormat( // "[WATER WARS]: Ignoring backup of {0} {1} since object is marked as already deleted", Name, UUID); return; } if ((RootPart.Flags & PrimFlags.TemporaryOnRez) != 0) return; // Since this is the top of the section of call stack for backing up a particular scene object, don't let // any exception propogate upwards. try { if (!m_scene.ShuttingDown || // if shutting down then there will be nothing to handle the return so leave till next restart m_scene.LoginsDisabled || // We're starting up or doing maintenance, don't mess with things m_scene.LoadingPrims) // Land may not be valid yet { ILandObject parcel = m_scene.LandChannel.GetLandObject( m_rootPart.GroupPosition.X, m_rootPart.GroupPosition.Y); if (parcel != null && parcel.LandData != null && parcel.LandData.OtherCleanTime != 0) { if (parcel.LandData.OwnerID != OwnerID && (parcel.LandData.GroupID != GroupID || parcel.LandData.GroupID == UUID.Zero)) { if ((DateTime.UtcNow - RootPart.Rezzed).TotalMinutes > parcel.LandData.OtherCleanTime) { DetachFromBackup(); m_log.DebugFormat( "[SCENE OBJECT GROUP]: Returning object {0} due to parcel autoreturn", RootPart.UUID); m_scene.AddReturn(OwnerID == GroupID ? LastOwnerID : OwnerID, Name, AbsolutePosition, "parcel autoreturn"); m_scene.DeRezObjects(null, new List() { RootPart.LocalId }, UUID.Zero, DeRezAction.Return, UUID.Zero); return; } } } } if (m_scene.UseBackup && HasGroupChanged) { // don't backup while it's selected or you're asking for changes mid stream. if (isTimeToPersist() || forcedBackup) { if (m_rootPart.PhysActor != null && (!m_rootPart.PhysActor.IsPhysical)) { // Possible ghost prim if (m_rootPart.PhysActor.Position != m_rootPart.GroupPosition) { foreach (SceneObjectPart part in m_parts.GetArray()) { // Re-set physics actor positions and // orientations part.GroupPosition = m_rootPart.GroupPosition; } } } // m_log.DebugFormat( // "[SCENE]: Storing {0}, {1} in {2}", // Name, UUID, m_scene.RegionInfo.RegionName); if (RootPart.Shape.PCode == 9 && RootPart.Shape.State != 0) { RootPart.Shape.State = 0; ScheduleGroupForFullUpdate(); } SceneObjectGroup backup_group = Copy(false); backup_group.RootPart.Velocity = RootPart.Velocity; backup_group.RootPart.Acceleration = RootPart.Acceleration; backup_group.RootPart.AngularVelocity = RootPart.AngularVelocity; backup_group.RootPart.ParticleSystem = RootPart.ParticleSystem; HasGroupChanged = false; HasGroupChangedDueToDelink = false; m_scene.EventManager.TriggerOnSceneObjectPreSave(backup_group, this); datastore.StoreObject(backup_group, m_scene.RegionInfo.RegionID); backup_group.ForEachPart(delegate(SceneObjectPart part) { if (part.KeyframeMotion != null) { part.KeyframeMotion = KeyframeMotion.FromData(backup_group, part.KeyframeMotion.Serialize()); part.KeyframeMotion.UpdateSceneObject(this); } part.Inventory.ProcessInventoryBackup(datastore); }); backup_group = null; } // else // { // m_log.DebugFormat( // "[SCENE]: Did not update persistence of object {0} {1}, selected = {2}", // Name, UUID, IsSelected); // } } } catch (Exception e) { m_log.ErrorFormat( "[SCENE]: Storing of {0}, {1} in {2} failed with exception {3}{4}", Name, UUID, m_scene.RegionInfo.RegionName, e.Message, e.StackTrace); } } #endregion /// /// Send the parts of this SOG to a single client /// /// /// Used when the client initially connects and when client sends RequestPrim packet /// /// public void SendFullUpdateToClient(IClientAPI remoteClient) { RootPart.SendFullUpdate(remoteClient); SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) { SceneObjectPart part = parts[i]; if (part != RootPart) part.SendFullUpdate(remoteClient); } } #region Copying /// /// Duplicates this object, including operations such as physics set up and attaching to the backup event. /// /// True if the duplicate will immediately be in the scene, false otherwise /// public SceneObjectGroup Copy(bool userExposed) { m_dupeInProgress = true; SceneObjectGroup dupe = (SceneObjectGroup)MemberwiseClone(); dupe.m_isBackedUp = false; dupe.m_parts = new MapAndArray(); // new group as no sitting avatars dupe.m_linkedAvatars = new List(); // Warning, The following code related to previousAttachmentStatus is needed so that clones of // attachments do not bordercross while they're being duplicated. This is hacktastic! // Normally, setting AbsolutePosition will bordercross a prim if it's outside the region! // unless IsAttachment is true!, so to prevent border crossing, we save it's attachment state // (which should be false anyway) set it as an Attachment and then set it's Absolute Position, // then restore it's attachment state // This is only necessary when userExposed is false! bool previousAttachmentStatus = dupe.IsAttachment; if (!userExposed) dupe.IsAttachment = true; dupe.AbsolutePosition = new Vector3(AbsolutePosition.X, AbsolutePosition.Y, AbsolutePosition.Z); if (!userExposed) { dupe.IsAttachment = previousAttachmentStatus; } dupe.CopyRootPart(m_rootPart, OwnerID, GroupID, userExposed); dupe.m_rootPart.LinkNum = m_rootPart.LinkNum; if (userExposed) dupe.m_rootPart.TrimPermissions(); List partList = new List(m_parts.GetArray()); partList.Sort(delegate(SceneObjectPart p1, SceneObjectPart p2) { return p1.LinkNum.CompareTo(p2.LinkNum); } ); foreach (SceneObjectPart part in partList) { SceneObjectPart newPart; if (part.UUID != m_rootPart.UUID) { newPart = dupe.CopyPart(part, OwnerID, GroupID, userExposed); newPart.LinkNum = part.LinkNum; if (userExposed) newPart.ParentID = dupe.m_rootPart.LocalId; } else { newPart = dupe.m_rootPart; } /* bool isphys = ((newPart.Flags & PrimFlags.Physics) != 0); bool isphan = ((newPart.Flags & PrimFlags.Phantom) != 0); // Need to duplicate the physics actor as well if (userExposed && (isphys || !isphan || newPart.VolumeDetectActive)) { PrimitiveBaseShape pbs = newPart.Shape; newPart.PhysActor = m_scene.PhysicsScene.AddPrimShape( string.Format("{0}/{1}", newPart.Name, newPart.UUID), pbs, newPart.AbsolutePosition, newPart.Scale, newPart.GetWorldRotation(), isphys, isphan, newPart.LocalId); newPart.DoPhysicsPropertyUpdate(isphys, true); */ if (userExposed) newPart.ApplyPhysics((uint)newPart.Flags,newPart.VolumeDetectActive,true); // } } if (userExposed) { // done above dupe.UpdateParentIDs(); if (dupe.m_rootPart.PhysActor != null) dupe.m_rootPart.PhysActor.Building = false; // tell physics to finish building dupe.HasGroupChanged = true; dupe.AttachToBackup(); ScheduleGroupForFullUpdate(); } m_dupeInProgress = false; return dupe; } /// /// Copy the given part as the root part of this scene object. /// /// /// /// public void CopyRootPart(SceneObjectPart part, UUID cAgentID, UUID cGroupID, bool userExposed) { // SetRootPart(part.Copy(m_scene.AllocateLocalId(), OwnerID, GroupID, 0, userExposed)); // give newpart a new local ID lettng old part keep same SceneObjectPart newpart = part.Copy(part.LocalId, OwnerID, GroupID, 0, userExposed); newpart.LocalId = m_scene.AllocateLocalId(); SetRootPart(newpart); if (userExposed) RootPart.Velocity = Vector3.Zero; // In case source is moving } public void ScriptSetPhysicsStatus(bool usePhysics) { if (usePhysics) { if (RootPart.KeyframeMotion != null) RootPart.KeyframeMotion.Stop(); RootPart.KeyframeMotion = null; } UpdatePrimFlags(RootPart.LocalId, usePhysics, IsTemporary, IsPhantom, IsVolumeDetect); } public void ScriptSetTemporaryStatus(bool makeTemporary) { UpdatePrimFlags(RootPart.LocalId, UsesPhysics, makeTemporary, IsPhantom, IsVolumeDetect); } public void ScriptSetPhantomStatus(bool makePhantom) { UpdatePrimFlags(RootPart.LocalId, UsesPhysics, IsTemporary, makePhantom, IsVolumeDetect); } public void ScriptSetVolumeDetect(bool makeVolumeDetect) { UpdatePrimFlags(RootPart.LocalId, UsesPhysics, IsTemporary, IsPhantom, makeVolumeDetect); /* ScriptSetPhantomStatus(false); // What ever it was before, now it's not phantom anymore if (PhysActor != null) // Should always be the case now { PhysActor.SetVolumeDetect(param); } if (param != 0) AddFlag(PrimFlags.Phantom); ScheduleFullUpdate(); */ } public void applyImpulse(Vector3 impulse) { if (IsAttachment) { ScenePresence avatar = m_scene.GetScenePresence(AttachedAvatar); if (avatar != null) { avatar.PushForce(impulse); } } else { PhysicsActor pa = RootPart.PhysActor; if (pa != null) { // false to be applied as a impulse pa.AddForce(impulse, false); m_scene.PhysicsScene.AddPhysicsActorTaint(pa); } } } public void ApplyAngularImpulse(Vector3 impulse) { PhysicsActor pa = RootPart.PhysActor; if (pa != null) { if (!IsAttachment) { // false to be applied as a impulse pa.AddAngularForce(impulse, false); m_scene.PhysicsScene.AddPhysicsActorTaint(pa); } } } public Vector3 GetTorque() { return RootPart.Torque; } // This is used by both Double-Click Auto-Pilot and llMoveToTarget() in an attached object public void moveToTarget(Vector3 target, float tau) { if (IsAttachment) { ScenePresence avatar = m_scene.GetScenePresence(AttachedAvatar); if (avatar != null) { avatar.MoveToTarget(target, false, false); } } else { PhysicsActor pa = RootPart.PhysActor; if (pa != null) { pa.PIDTarget = target; pa.PIDTau = tau; pa.PIDActive = true; } } } public void stopMoveToTarget() { PhysicsActor pa = RootPart.PhysActor; if (pa != null) pa.PIDActive = false; } public void rotLookAt(Quaternion target, float strength, float damping) { SceneObjectPart rootpart = m_rootPart; if (rootpart != null) { if (IsAttachment) { /* ScenePresence avatar = m_scene.GetScenePresence(rootpart.AttachedAvatar); if (avatar != null) { Rotate the Av? } */ } else { if (rootpart.PhysActor != null) { // APID must be implemented in your physics system for this to function. rootpart.PhysActor.APIDTarget = new Quaternion(target.X, target.Y, target.Z, target.W); rootpart.PhysActor.APIDStrength = strength; rootpart.PhysActor.APIDDamping = damping; rootpart.PhysActor.APIDActive = true; } } } } public void stopLookAt() { SceneObjectPart rootpart = m_rootPart; if (rootpart != null) { if (rootpart.PhysActor != null) { // APID must be implemented in your physics system for this to function. rootpart.PhysActor.APIDActive = false; } } } /// /// 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) { PhysicsActor pa = RootPart.PhysActor; if (pa != null) { if (height != 0f) { pa.PIDHoverHeight = height; pa.PIDHoverType = hoverType; pa.PIDHoverTau = tau; pa.PIDHoverActive = true; } else { pa.PIDHoverActive = false; } } } /// /// Set the owner of the root part. /// /// /// /// public void SetRootPartOwner(SceneObjectPart part, UUID cAgentID, UUID cGroupID) { part.LastOwnerID = part.OwnerID; part.OwnerID = cAgentID; part.GroupID = cGroupID; if (part.OwnerID != cAgentID) { // Apply Next Owner Permissions if we're not bypassing permissions if (!m_scene.Permissions.BypassPermissions()) ApplyNextOwnerPermissions(); } part.ScheduleFullUpdate(); } /// /// Make a copy of the given part. /// /// /// /// public SceneObjectPart CopyPart(SceneObjectPart part, UUID cAgentID, UUID cGroupID, bool userExposed) { // give new ID to the new part, letting old keep original // SceneObjectPart newPart = part.Copy(m_scene.AllocateLocalId(), OwnerID, GroupID, m_parts.Count, userExposed); SceneObjectPart newPart = part.Copy(part.LocalId, OwnerID, GroupID, m_parts.Count, userExposed); newPart.LocalId = m_scene.AllocateLocalId(); newPart.SetParent(this); AddPart(newPart); SetPartAsNonRoot(newPart); return newPart; } /// /// Reset the UUIDs for all the prims that make up this group. /// /// /// This is called by methods which want to add a new group to an existing scene, in order /// to ensure that there are no clashes with groups already present. /// public void ResetIDs() { lock (m_parts.SyncRoot) { List partsList = new List(m_parts.GetArray()); m_parts.Clear(); foreach (SceneObjectPart part in partsList) { part.ResetIDs(part.LinkNum); // Don't change link nums m_parts.Add(part.UUID, part); } } } /// /// /// /// public void ServiceObjectPropertiesFamilyRequest(IClientAPI remoteClient, UUID AgentID, uint RequestFlags) { remoteClient.SendObjectPropertiesFamilyData(RootPart, RequestFlags); // remoteClient.SendObjectPropertiesFamilyData(RequestFlags, RootPart.UUID, RootPart.OwnerID, RootPart.GroupID, RootPart.BaseMask, // RootPart.OwnerMask, RootPart.GroupMask, RootPart.EveryoneMask, RootPart.NextOwnerMask, // RootPart.OwnershipCost, RootPart.ObjectSaleType, RootPart.SalePrice, RootPart.Category, // RootPart.CreatorID, RootPart.Name, RootPart.Description); } public void SetPartOwner(SceneObjectPart part, UUID cAgentID, UUID cGroupID) { part.OwnerID = cAgentID; part.GroupID = cGroupID; } #endregion public override void Update() { // Check that the group was not deleted before the scheduled update // FIXME: This is merely a temporary measure to reduce the incidence of failure when // an object has been deleted from a scene before update was processed. // A more fundamental overhaul of the update mechanism is required to eliminate all // the race conditions. if (IsDeleted) return; // Even temporary objects take part in physics (e.g. temp-on-rez bullets) //if ((RootPart.Flags & PrimFlags.TemporaryOnRez) != 0) // return; // If we somehow got here to updating the SOG and its root part is not scheduled for update, // check to see if the physical position or rotation warrant an update. if (m_rootPart.UpdateFlag == UpdateRequired.NONE) { bool UsePhysics = ((RootPart.Flags & PrimFlags.Physics) != 0); if (UsePhysics && !AbsolutePosition.ApproxEquals(lastPhysGroupPos, 0.02f)) { m_rootPart.UpdateFlag = UpdateRequired.TERSE; lastPhysGroupPos = AbsolutePosition; } if (UsePhysics && !GroupRotation.ApproxEquals(lastPhysGroupRot, 0.1f)) { m_rootPart.UpdateFlag = UpdateRequired.TERSE; lastPhysGroupRot = GroupRotation; } } SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) { SceneObjectPart part = parts[i]; if (!IsSelected) part.UpdateLookAt(); part.SendScheduledUpdates(); } } /// /// Schedule a full update for this scene object /// public void ScheduleGroupForFullUpdate() { // if (IsAttachment) // m_log.DebugFormat("[SOG]: Scheduling full update for {0} {1}", Name, LocalId); checkAtTargets(); RootPart.ScheduleFullUpdate(); SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) { SceneObjectPart part = parts[i]; if (part != RootPart) part.ScheduleFullUpdate(); } } /// /// Schedule a terse update for this scene object /// public void ScheduleGroupForTerseUpdate() { // m_log.DebugFormat("[SOG]: Scheduling terse update for {0} {1}", Name, UUID); SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) parts[i].ScheduleTerseUpdate(); } /// /// Immediately send a full update for this scene object. /// public void SendGroupFullUpdate() { if (IsDeleted) return; // m_log.DebugFormat("[SOG]: Sending immediate full group update for {0} {1}", Name, UUID); RootPart.SendFullUpdateToAllClients(); SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) { SceneObjectPart part = parts[i]; if (part != RootPart) part.SendFullUpdateToAllClients(); } } /// /// Immediately send an update for this scene object's root prim only. /// This is for updates regarding the object as a whole, and none of its parts in particular. /// Note: this may not be used by opensim (it probably should) but it's used by /// external modules. /// public void SendGroupRootTerseUpdate() { if (IsDeleted) return; RootPart.SendTerseUpdateToAllClients(); } public void QueueForUpdateCheck() { if (m_scene == null) // Need to check here as it's null during object creation return; m_scene.SceneGraph.AddToUpdateList(this); } /// /// Immediately send a terse update for this scene object. /// public void SendGroupTerseUpdate() { if (IsDeleted) return; SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) parts[i].SendTerseUpdateToAllClients(); } /// /// Send metadata about the root prim (name, description, sale price, permissions, etc.) to a client. /// /// public void SendPropertiesToClient(IClientAPI client) { m_rootPart.SendPropertiesToClient(client); } #region SceneGroupPart Methods /// /// Get the child part by LinkNum /// /// /// null if no child part with that linknum or child part public SceneObjectPart GetLinkNumPart(int linknum) { SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) { if (parts[i].LinkNum == linknum) return parts[i]; } return null; } /// /// Get a part with a given UUID /// /// /// null if a part with the primID was not found public SceneObjectPart GetPart(UUID primID) { SceneObjectPart childPart; m_parts.TryGetValue(primID, out childPart); return childPart; } /// /// Get a part with a given local ID /// /// /// null if a part with the local ID was not found public SceneObjectPart GetPart(uint localID) { SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) { if (parts[i].LocalId == localID) return parts[i]; } return null; } #endregion #region Packet Handlers /// /// Link the prims in a given group to this group /// /// /// Do not call this method directly - use Scene.LinkObjects() instead to avoid races between threads. /// FIXME: There are places where scripts call these methods directly without locking. This is a potential race condition. /// /// The group of prims which should be linked to this group public void LinkToGroup(SceneObjectGroup objectGroup) { LinkToGroup(objectGroup, false); } public void LinkToGroup(SceneObjectGroup objectGroup, bool insert) { // m_log.DebugFormat( // "[SCENE OBJECT GROUP]: Linking group with root part {0}, {1} to group with root part {2}, {3}", // objectGroup.RootPart.Name, objectGroup.RootPart.UUID, RootPart.Name, RootPart.UUID); // Linking to ourselves is not a valid operation. if (objectGroup == this) return; SceneObjectPart linkPart = objectGroup.m_rootPart; if (m_rootPart.PhysActor != null) m_rootPart.PhysActor.Building = true; if (linkPart.PhysActor != null) linkPart.PhysActor.Building = true; // physics flags from group to be applied to linked parts bool grpusephys = UsesPhysics; bool grptemporary = IsTemporary; Vector3 oldGroupPosition = linkPart.GroupPosition; Quaternion oldRootRotation = linkPart.RotationOffset; linkPart.OffsetPosition = linkPart.GroupPosition - AbsolutePosition; linkPart.ParentID = m_rootPart.LocalId; linkPart.GroupPosition = AbsolutePosition; Vector3 axPos = linkPart.OffsetPosition; Quaternion parentRot = m_rootPart.RotationOffset; axPos *= Quaternion.Conjugate(parentRot); linkPart.OffsetPosition = axPos; Quaternion oldRot = linkPart.RotationOffset; Quaternion newRot = Quaternion.Conjugate(parentRot) * oldRot; linkPart.RotationOffset = newRot; // linkPart.ParentID = m_rootPart.LocalId; done above if (m_rootPart.LinkNum == 0) m_rootPart.LinkNum = 1; lock (m_parts.SyncRoot) { int linkNum; if (insert) { linkNum = 2; foreach (SceneObjectPart part in Parts) { if (part.LinkNum > 1) part.LinkNum++; } } else { linkNum = PrimCount + 1; } m_parts.Add(linkPart.UUID, linkPart); linkPart.SetParent(this); linkPart.CreateSelected = true; // let physics know preserve part volume dtc messy since UpdatePrimFlags doesn't look to parent changes for now linkPart.UpdatePrimFlags(grpusephys, grptemporary, (IsPhantom || (linkPart.Flags & PrimFlags.Phantom) != 0), linkPart.VolumeDetectActive, true); if (linkPart.PhysActor != null && m_rootPart.PhysActor != null && m_rootPart.PhysActor.IsPhysical) { linkPart.PhysActor.link(m_rootPart.PhysActor); this.Scene.PhysicsScene.AddPhysicsActorTaint(linkPart.PhysActor); } linkPart.LinkNum = linkNum++; linkPart.UpdatePrimFlags(UsesPhysics, IsTemporary, IsPhantom, IsVolumeDetect, false); SceneObjectPart[] ogParts = objectGroup.Parts; Array.Sort(ogParts, delegate(SceneObjectPart a, SceneObjectPart b) { return a.LinkNum - b.LinkNum; }); for (int i = 0; i < ogParts.Length; i++) { SceneObjectPart part = ogParts[i]; if (part.UUID != objectGroup.m_rootPart.UUID) { LinkNonRootPart(part, oldGroupPosition, oldRootRotation, linkNum++); // let physics know part.UpdatePrimFlags(grpusephys, grptemporary, (IsPhantom || (part.Flags & PrimFlags.Phantom) != 0), part.VolumeDetectActive, true); if (part.PhysActor != null && m_rootPart.PhysActor != null && m_rootPart.PhysActor.IsPhysical) { part.PhysActor.link(m_rootPart.PhysActor); this.Scene.PhysicsScene.AddPhysicsActorTaint(part.PhysActor); } } part.ClearUndoState(); } } m_scene.UnlinkSceneObject(objectGroup, true); objectGroup.IsDeleted = true; objectGroup.m_parts.Clear(); // Can't do this yet since backup still makes use of the root part without any synchronization // objectGroup.m_rootPart = null; AttachToBackup(); // Here's the deal, this is ABSOLUTELY CRITICAL so the physics scene gets the update about the // position of linkset prims. IF YOU CHANGE THIS, YOU MUST TEST colliding with just linked and // unmoved prims! ResetChildPrimPhysicsPositions(); if (m_rootPart.PhysActor != null) m_rootPart.PhysActor.Building = false; //HasGroupChanged = true; //ScheduleGroupForFullUpdate(); } /// /// Delink the given prim from this group. The delinked prim is established as /// an independent SceneObjectGroup. /// /// /// FIXME: This method should not be called directly since it bypasses update locking, allowing a potential race /// condition. But currently there is no /// alternative method that does take a lonk to delink a single prim. /// /// /// The object group of the newly delinked prim. Null if part could not be found public SceneObjectGroup DelinkFromGroup(uint partID) { return DelinkFromGroup(partID, true); } /// /// Delink the given prim from this group. The delinked prim is established as /// an independent SceneObjectGroup. /// /// /// FIXME: This method should not be called directly since it bypasses update locking, allowing a potential race /// condition. But currently there is no /// alternative method that does take a lonk to delink a single prim. /// /// /// /// The object group of the newly delinked prim. Null if part could not be found public SceneObjectGroup DelinkFromGroup(uint partID, bool sendEvents) { SceneObjectPart linkPart = GetPart(partID); if (linkPart != null) { return DelinkFromGroup(linkPart, sendEvents); } else { m_log.WarnFormat("[SCENE OBJECT GROUP]: " + "DelinkFromGroup(): Child prim {0} not found in object {1}, {2}", partID, LocalId, UUID); return null; } } /// /// Delink the given prim from this group. The delinked prim is established as /// an independent SceneObjectGroup. /// /// /// FIXME: This method should not be called directly since it bypasses update locking, allowing a potential race /// condition. But currently there is no /// alternative method that does take a lonk to delink a single prim. /// /// /// /// The object group of the newly delinked prim. public SceneObjectGroup DelinkFromGroup(SceneObjectPart linkPart, bool sendEvents) { // m_log.DebugFormat( // "[SCENE OBJECT GROUP]: Delinking part {0}, {1} from group with root part {2}, {3}", // linkPart.Name, linkPart.UUID, RootPart.Name, RootPart.UUID); if (m_rootPart.PhysActor != null) m_rootPart.PhysActor.Building = true; linkPart.ClearUndoState(); Quaternion worldRot = linkPart.GetWorldRotation(); // Remove the part from this object lock (m_parts.SyncRoot) { m_parts.Remove(linkPart.UUID); SceneObjectPart[] parts = m_parts.GetArray(); if (parts.Length == 1 && RootPart != null) { // Single prim left RootPart.LinkNum = 0; } else { for (int i = 0; i < parts.Length; i++) { SceneObjectPart part = parts[i]; if (part.LinkNum > linkPart.LinkNum) part.LinkNum--; } } } linkPart.ParentID = 0; linkPart.LinkNum = 0; PhysicsActor linkPartPa = linkPart.PhysActor; if (linkPartPa != null) m_scene.PhysicsScene.RemovePrim(linkPartPa); // We need to reset the child part's position // ready for life as a separate object after being a part of another object Quaternion parentRot = m_rootPart.RotationOffset; Vector3 axPos = linkPart.OffsetPosition; axPos *= parentRot; linkPart.OffsetPosition = new Vector3(axPos.X, axPos.Y, axPos.Z); linkPart.GroupPosition = AbsolutePosition + linkPart.OffsetPosition; linkPart.OffsetPosition = new Vector3(0, 0, 0); linkPart.RotationOffset = worldRot; SceneObjectGroup objectGroup = new SceneObjectGroup(linkPart); m_scene.AddNewSceneObject(objectGroup, true); if (sendEvents) linkPart.TriggerScriptChangedEvent(Changed.LINK); linkPart.Rezzed = RootPart.Rezzed; // When we delete a group, we currently have to force persist to the database if the object id has changed // (since delete works by deleting all rows which have a given object id) // this is as it seems to be in sl now if(linkPart.PhysicsShapeType == (byte)PhysShapeType.none) linkPart.PhysicsShapeType = linkPart.DefaultPhysicsShapeType(); // root prims can't have type none for now if (m_rootPart.PhysActor != null) m_rootPart.PhysActor.Building = false; objectGroup.HasGroupChangedDueToDelink = true; return objectGroup; } /// /// Stop this object from being persisted over server restarts. /// /// public virtual void DetachFromBackup() { m_scene.SceneGraph.FireDetachFromBackup(this); if (m_isBackedUp && Scene != null) m_scene.EventManager.OnBackup -= ProcessBackup; m_isBackedUp = false; } private void LinkNonRootPart(SceneObjectPart part, Vector3 oldGroupPosition, Quaternion oldGroupRotation, int linkNum) { Quaternion parentRot = oldGroupRotation; Quaternion oldRot = part.RotationOffset; Quaternion worldRot = parentRot * oldRot; parentRot = oldGroupRotation; Vector3 axPos = part.OffsetPosition; axPos *= parentRot; part.OffsetPosition = axPos; Vector3 newPos = oldGroupPosition + part.OffsetPosition; part.GroupPosition = newPos; part.OffsetPosition = Vector3.Zero; part.RotationOffset = worldRot; part.SetParent(this); part.ParentID = m_rootPart.LocalId; m_parts.Add(part.UUID, part); part.LinkNum = linkNum; part.OffsetPosition = newPos - AbsolutePosition; Quaternion rootRotation = m_rootPart.RotationOffset; Vector3 pos = part.OffsetPosition; pos *= Quaternion.Conjugate(rootRotation); part.OffsetPosition = pos; parentRot = m_rootPart.RotationOffset; oldRot = part.RotationOffset; Quaternion newRot = Quaternion.Conjugate(parentRot) * worldRot; part.RotationOffset = newRot; part.UpdatePrimFlags(UsesPhysics, IsTemporary, IsPhantom, IsVolumeDetect, false); } /// /// If object is physical, apply force to move it around /// If object is not physical, just put it at the resulting location /// /// Always seems to be 0,0,0, so ignoring /// New position. We do the math here to turn it into a force /// public void GrabMovement(Vector3 offset, Vector3 pos, IClientAPI remoteClient) { if (m_scene.EventManager.TriggerGroupMove(UUID, pos)) { PhysicsActor pa = m_rootPart.PhysActor; if (pa != null) { if (pa.IsPhysical) { if (!m_rootPart.BlockGrab) { /* Vector3 llmoveforce = pos - AbsolutePosition; Vector3 grabforce = llmoveforce; grabforce = (grabforce / 10) * pa.Mass; */ // empirically convert distance diference to a impulse Vector3 grabforce = pos - AbsolutePosition; grabforce = grabforce * (pa.Mass/ 10.0f); pa.AddForce(grabforce, false); m_scene.PhysicsScene.AddPhysicsActorTaint(pa); } } else { //NonPhysicalGrabMovement(pos); } } else { //NonPhysicalGrabMovement(pos); } } } public void NonPhysicalGrabMovement(Vector3 pos) { AbsolutePosition = pos; m_rootPart.SendTerseUpdateToAllClients(); } /// /// If object is physical, prepare for spinning torques (set flag to save old orientation) /// /// Rotation. We do the math here to turn it into a torque /// public void SpinStart(IClientAPI remoteClient) { if (m_scene.EventManager.TriggerGroupSpinStart(UUID)) { PhysicsActor pa = m_rootPart.PhysActor; if (pa != null) { if (pa.IsPhysical) { m_rootPart.IsWaitingForFirstSpinUpdatePacket = true; } } } } /// /// If object is physical, apply torque to spin it around /// /// Rotation. We do the math here to turn it into a torque /// public void SpinMovement(Quaternion newOrientation, IClientAPI remoteClient) { // The incoming newOrientation, sent by the client, "seems" to be the // desired target orientation. This needs further verification; in particular, // one would expect that the initial incoming newOrientation should be // fairly close to the original prim's physical orientation, // m_rootPart.PhysActor.Orientation. This however does not seem to be the // case (might just be an issue with different quaternions representing the // same rotation, or it might be a coordinate system issue). // // Since it's not clear what the relationship is between the PhysActor.Orientation // and the incoming orientations sent by the client, we take an alternative approach // of calculating the delta rotation between the orientations being sent by the // client. (Since a spin is invoked by ctrl+shift+drag in the client, we expect // a steady stream of several new orientations coming in from the client.) // This ensures that the delta rotations are being calculated from self-consistent // pairs of old/new rotations. Given the delta rotation, we apply a torque around // the delta rotation axis, scaled by the object mass times an arbitrary scaling // factor (to ensure the resulting torque is not "too strong" or "too weak"). // // Ideally we need to calculate (probably iteratively) the exact torque or series // of torques needed to arrive exactly at the destination orientation. However, since // it is not yet clear how to map the destination orientation (provided by the viewer) // into PhysActor orientations (needed by the physics engine), we omit this step. // This means that the resulting torque will at least be in the correct direction, // but it will result in over-shoot or under-shoot of the target orientation. // For the end user, this means that ctrl+shift+drag can be used for relative, // but not absolute, adjustments of orientation for physical prims. if (m_scene.EventManager.TriggerGroupSpin(UUID, newOrientation)) { PhysicsActor pa = m_rootPart.PhysActor; if (pa != null) { if (pa.IsPhysical) { if (m_rootPart.IsWaitingForFirstSpinUpdatePacket) { // first time initialization of "old" orientation for calculation of delta rotations m_rootPart.SpinOldOrientation = newOrientation; m_rootPart.IsWaitingForFirstSpinUpdatePacket = false; } else { // save and update old orientation Quaternion old = m_rootPart.SpinOldOrientation; m_rootPart.SpinOldOrientation = newOrientation; //m_log.Error("[SCENE OBJECT GROUP]: Old orientation is " + old); //m_log.Error("[SCENE OBJECT GROUP]: Incoming new orientation is " + newOrientation); // compute difference between previous old rotation and new incoming rotation Quaternion minimalRotationFromQ1ToQ2 = Quaternion.Inverse(old) * newOrientation; float rotationAngle; Vector3 rotationAxis; minimalRotationFromQ1ToQ2.GetAxisAngle(out rotationAxis, out rotationAngle); rotationAxis.Normalize(); //m_log.Error("SCENE OBJECT GROUP]: rotation axis is " + rotationAxis); Vector3 spinforce = new Vector3(rotationAxis.X, rotationAxis.Y, rotationAxis.Z); spinforce = (spinforce/8) * pa.Mass; // 8 is an arbitrary torque scaling factor pa.AddAngularForce(spinforce,true); m_scene.PhysicsScene.AddPhysicsActorTaint(pa); } } else { //NonPhysicalSpinMovement(pos); } } else { //NonPhysicalSpinMovement(pos); } } } /// /// Set the name of a prim /// /// /// public void SetPartName(string name, uint localID) { SceneObjectPart part = GetPart(localID); if (part != null) { part.Name = name; } } public void SetPartDescription(string des, uint localID) { SceneObjectPart part = GetPart(localID); if (part != null) { part.Description = des; } } public void SetPartText(string text, uint localID) { SceneObjectPart part = GetPart(localID); if (part != null) { part.SetText(text); } } public void SetPartText(string text, UUID partID) { SceneObjectPart part = GetPart(partID); if (part != null) { part.SetText(text); } } public string GetPartName(uint localID) { SceneObjectPart part = GetPart(localID); if (part != null) { return part.Name; } return String.Empty; } public string GetPartDescription(uint localID) { SceneObjectPart part = GetPart(localID); if (part != null) { return part.Description; } return String.Empty; } /// /// Update prim flags for this group. /// /// /// /// /// /// public void UpdatePrimFlags(uint localID, bool UsePhysics, bool SetTemporary, bool SetPhantom, bool SetVolumeDetect) { SceneObjectPart selectionPart = GetPart(localID); if (SetTemporary && Scene != null) { DetachFromBackup(); // Remove from database and parcel prim count // m_scene.DeleteFromStorage(UUID); m_scene.EventManager.TriggerParcelPrimCountTainted(); } if (selectionPart != null) { SceneObjectPart[] parts = m_parts.GetArray(); if (Scene != null) { for (int i = 0; i < parts.Length; i++) { SceneObjectPart part = parts[i]; if (part.Scale.X > m_scene.m_maxPhys || part.Scale.Y > m_scene.m_maxPhys || part.Scale.Z > m_scene.m_maxPhys ) { UsePhysics = false; // Reset physics break; } } } if (parts.Length > 1) { m_rootPart.UpdatePrimFlags(UsePhysics, SetTemporary, SetPhantom, SetVolumeDetect, true); for (int i = 0; i < parts.Length; i++) { if (parts[i].UUID != m_rootPart.UUID) parts[i].UpdatePrimFlags(UsePhysics, SetTemporary, SetPhantom, SetVolumeDetect, true); } if (m_rootPart.PhysActor != null) m_rootPart.PhysActor.Building = false; } else m_rootPart.UpdatePrimFlags(UsePhysics, SetTemporary, SetPhantom, SetVolumeDetect, false); } } public void UpdateExtraParam(uint localID, ushort type, bool inUse, byte[] data) { SceneObjectPart part = GetPart(localID); if (part != null) { part.UpdateExtraParam(type, inUse, data); } } /// /// Gets the number of parts /// /// public int GetPartCount() { return Parts.Count(); } /// /// Update the texture entry for this part /// /// /// public void UpdateTextureEntry(uint localID, byte[] textureEntry) { SceneObjectPart part = GetPart(localID); if (part != null) { part.UpdateTextureEntry(textureEntry); } } public void UpdatePermissions(UUID AgentID, byte field, uint localID, uint mask, byte addRemTF) { SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) parts[i].UpdatePermissions(AgentID, field, localID, mask, addRemTF); HasGroupChanged = true; // Send the group's properties to all clients once all parts are updated IClientAPI client; if (Scene.TryGetClient(AgentID, out client)) SendPropertiesToClient(client); } #endregion #region Shape /// /// /// /// public void UpdateShape(ObjectShapePacket.ObjectDataBlock shapeBlock, uint localID) { SceneObjectPart part = GetPart(localID); if (part != null) { part.UpdateShape(shapeBlock); PhysicsActor pa = m_rootPart.PhysActor; if (pa != null) m_scene.PhysicsScene.AddPhysicsActorTaint(pa); } } #endregion #region Resize /// /// Resize the entire group of prims. /// /// public void GroupResize(Vector3 scale) { scale.X = Math.Min(scale.X, Scene.m_maxNonphys); scale.Y = Math.Min(scale.Y, Scene.m_maxNonphys); scale.Z = Math.Min(scale.Z, Scene.m_maxNonphys); PhysicsActor pa = m_rootPart.PhysActor; if (pa != null && pa.IsPhysical) { scale.X = Math.Min(scale.X, Scene.m_maxPhys); scale.Y = Math.Min(scale.Y, Scene.m_maxPhys); scale.Z = Math.Min(scale.Z, Scene.m_maxPhys); } float x = (scale.X / RootPart.Scale.X); float y = (scale.Y / RootPart.Scale.Y); float z = (scale.Z / RootPart.Scale.Z); SceneObjectPart[] parts; if (x > 1.0f || y > 1.0f || z > 1.0f) { parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) { SceneObjectPart obPart = parts[i]; if (obPart.UUID != m_rootPart.UUID) { Vector3 oldSize = new Vector3(obPart.Scale); float f = 1.0f; float a = 1.0f; if (pa != null && pa.IsPhysical) { if (oldSize.X * x > m_scene.m_maxPhys) { f = m_scene.m_maxPhys / oldSize.X; a = f / x; x *= a; y *= a; z *= a; } if (oldSize.Y * y > m_scene.m_maxPhys) { f = m_scene.m_maxPhys / oldSize.Y; a = f / y; x *= a; y *= a; z *= a; } if (oldSize.Z * z > m_scene.m_maxPhys) { f = m_scene.m_maxPhys / oldSize.Z; a = f / z; x *= a; y *= a; z *= a; } } else { if (oldSize.X * x > m_scene.m_maxNonphys) { f = m_scene.m_maxNonphys / oldSize.X; a = f / x; x *= a; y *= a; z *= a; } if (oldSize.Y * y > m_scene.m_maxNonphys) { f = m_scene.m_maxNonphys / oldSize.Y; a = f / y; x *= a; y *= a; z *= a; } if (oldSize.Z * z > m_scene.m_maxNonphys) { f = m_scene.m_maxNonphys / oldSize.Z; a = f / z; x *= a; y *= a; z *= a; } } } } } Vector3 prevScale = RootPart.Scale; prevScale.X *= x; prevScale.Y *= y; prevScale.Z *= z; RootPart.Resize(prevScale); parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) { SceneObjectPart obPart = parts[i]; if (obPart.UUID != m_rootPart.UUID) { Vector3 currentpos = new Vector3(obPart.OffsetPosition); currentpos.X *= x; currentpos.Y *= y; currentpos.Z *= z; Vector3 newSize = new Vector3(obPart.Scale); newSize.X *= x; newSize.Y *= y; newSize.Z *= z; obPart.Resize(newSize); obPart.UpdateOffSet(currentpos); } HasGroupChanged = true; m_rootPart.TriggerScriptChangedEvent(Changed.SCALE); ScheduleGroupForTerseUpdate(); } } #endregion #region Position /// /// Move this scene object /// /// public void UpdateGroupPosition(Vector3 pos) { if (m_scene.EventManager.TriggerGroupMove(UUID, pos)) { if (IsAttachment) { m_rootPart.AttachedPos = pos; } if (RootPart.GetStatusSandbox()) { if (Util.GetDistanceTo(RootPart.StatusSandboxPos, pos) > 10) { RootPart.ScriptSetPhysicsStatus(false); pos = AbsolutePosition; Scene.SimChat(Utils.StringToBytes("Hit Sandbox Limit"), ChatTypeEnum.DebugChannel, 0x7FFFFFFF, RootPart.AbsolutePosition, Name, UUID, false); } } AbsolutePosition = pos; HasGroupChanged = true; } //we need to do a terse update even if the move wasn't allowed // so that the position is reset in the client (the object snaps back) RootPart.ScheduleTerseUpdate(); } /// /// Update the position of a single part of this scene object /// /// /// /// public void UpdateSinglePosition(Vector3 pos, uint localID) { SceneObjectPart part = GetPart(localID); if (part != null) { // unlock parts position change if (m_rootPart.PhysActor != null) m_rootPart.PhysActor.Building = true; if (part.UUID == m_rootPart.UUID) { UpdateRootPosition(pos); } else { part.UpdateOffSet(pos); } if (m_rootPart.PhysActor != null) m_rootPart.PhysActor.Building = false; HasGroupChanged = true; } } /// /// Update just the root prim position in a linkset /// /// public void UpdateRootPosition(Vector3 pos) { // needs to be called with phys building true Vector3 newPos = new Vector3(pos.X, pos.Y, pos.Z); Vector3 oldPos = new Vector3(AbsolutePosition.X + m_rootPart.OffsetPosition.X, AbsolutePosition.Y + m_rootPart.OffsetPosition.Y, AbsolutePosition.Z + m_rootPart.OffsetPosition.Z); Vector3 diff = oldPos - newPos; Vector3 axDiff = new Vector3(diff.X, diff.Y, diff.Z); Quaternion partRotation = m_rootPart.RotationOffset; axDiff *= Quaternion.Inverse(partRotation); diff = axDiff; SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) { SceneObjectPart obPart = parts[i]; if (obPart.UUID != m_rootPart.UUID) obPart.OffsetPosition = obPart.OffsetPosition + diff; } AbsolutePosition = newPos; HasGroupChanged = true; if (m_rootPart.Undoing) { ScheduleGroupForFullUpdate(); } else { ScheduleGroupForTerseUpdate(); } } #endregion #region Rotation /// /// Update the rotation of the group. /// /// public void UpdateGroupRotationR(Quaternion rot) { m_rootPart.UpdateRotation(rot); /* this is done by rootpart RotationOffset set called by UpdateRotation PhysicsActor actor = m_rootPart.PhysActor; if (actor != null) { actor.Orientation = m_rootPart.RotationOffset; m_scene.PhysicsScene.AddPhysicsActorTaint(actor); } */ HasGroupChanged = true; ScheduleGroupForTerseUpdate(); } /// /// Update the position and rotation of a group simultaneously. /// /// /// public void UpdateGroupRotationPR(Vector3 pos, Quaternion rot) { m_rootPart.UpdateRotation(rot); PhysicsActor actor = m_rootPart.PhysActor; if (actor != null) { actor.Orientation = m_rootPart.RotationOffset; m_scene.PhysicsScene.AddPhysicsActorTaint(actor); } if (IsAttachment) { m_rootPart.AttachedPos = pos; } AbsolutePosition = pos; HasGroupChanged = true; ScheduleGroupForTerseUpdate(); } /// /// Update the rotation of a single prim within the group. /// /// /// public void UpdateSingleRotation(Quaternion rot, uint localID) { SceneObjectPart part = GetPart(localID); SceneObjectPart[] parts = m_parts.GetArray(); if (part != null) { if (m_rootPart.PhysActor != null) m_rootPart.PhysActor.Building = true; if (part.UUID == m_rootPart.UUID) { UpdateRootRotation(rot); } else { part.UpdateRotation(rot); } if (m_rootPart.PhysActor != null) m_rootPart.PhysActor.Building = false; } } /// /// Update the position and rotation simultaneously of a single prim within the group. /// /// /// public void UpdateSingleRotation(Quaternion rot, Vector3 pos, uint localID) { SceneObjectPart part = GetPart(localID); if (part != null) { if (m_rootPart.PhysActor != null) m_rootPart.PhysActor.Building = true; if (part.UUID == m_rootPart.UUID) { UpdateRootRotation(rot); AbsolutePosition = pos; } else { part.UpdateRotation(rot); part.OffsetPosition = pos; } if (m_rootPart.PhysActor != null) m_rootPart.PhysActor.Building = false; } } /// /// Update the rotation of just the root prim of a linkset. /// /// public void UpdateRootRotation(Quaternion rot) { // needs to be called with phys building true Quaternion axRot = rot; Quaternion oldParentRot = m_rootPart.RotationOffset; //Don't use UpdateRotation because it schedules an update prematurely m_rootPart.RotationOffset = rot; PhysicsActor pa = m_rootPart.PhysActor; if (pa != null) { pa.Orientation = m_rootPart.RotationOffset; m_scene.PhysicsScene.AddPhysicsActorTaint(pa); } SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) { SceneObjectPart prim = parts[i]; if (prim.UUID != m_rootPart.UUID) { Quaternion NewRot = oldParentRot * prim.RotationOffset; NewRot = Quaternion.Inverse(axRot) * NewRot; prim.RotationOffset = NewRot; Vector3 axPos = prim.OffsetPosition; axPos *= oldParentRot; axPos *= Quaternion.Inverse(axRot); prim.OffsetPosition = axPos; } } HasGroupChanged = true; ScheduleGroupForFullUpdate(); } private enum updatetype :int { none = 0, partterse = 1, partfull = 2, groupterse = 3, groupfull = 4 } public void doChangeObject(SceneObjectPart part, ObjectChangeData data) { // TODO this still as excessive *.Schedule*Update()s if (part != null && part.ParentGroup != null) { ObjectChangeType change = data.change; bool togroup = ((change & ObjectChangeType.Group) != 0); // bool uniform = ((what & ObjectChangeType.UniformScale) != 0); not in use SceneObjectGroup group = part.ParentGroup; PhysicsActor pha = group.RootPart.PhysActor; updatetype updateType = updatetype.none; if (togroup) { // related to group if ((change & (ObjectChangeType.Rotation | ObjectChangeType.Position)) != 0) { if ((change & ObjectChangeType.Rotation) != 0) { group.RootPart.UpdateRotation(data.rotation); updateType = updatetype.none; } if ((change & ObjectChangeType.Position) != 0) { if (IsAttachment || m_scene.Permissions.CanObjectEntry(group.UUID, false, data.position)) UpdateGroupPosition(data.position); updateType = updatetype.groupterse; } else // ugly rotation update of all parts { group.AbsolutePosition = AbsolutePosition; } } if ((change & ObjectChangeType.Scale) != 0) { if (pha != null) pha.Building = true; group.GroupResize(data.scale); updateType = updatetype.none; if (pha != null) pha.Building = false; } } else { // related to single prim in a link-set ( ie group) if (pha != null) pha.Building = true; // root part is special // parts offset positions or rotations need to change also if (part == group.RootPart) { if ((change & ObjectChangeType.Rotation) != 0) group.UpdateRootRotation(data.rotation); if ((change & ObjectChangeType.Position) != 0) group.UpdateRootPosition(data.position); if ((change & ObjectChangeType.Scale) != 0) part.Resize(data.scale); } else { if ((change & ObjectChangeType.Position) != 0) { part.OffsetPosition = data.position; updateType = updatetype.partterse; } if ((change & ObjectChangeType.Rotation) != 0) { part.UpdateRotation(data.rotation); updateType = updatetype.none; } if ((change & ObjectChangeType.Scale) != 0) { part.Resize(data.scale); updateType = updatetype.none; } } if (pha != null) pha.Building = false; } if (updateType != updatetype.none) { group.HasGroupChanged = true; switch (updateType) { case updatetype.partterse: part.ScheduleTerseUpdate(); break; case updatetype.partfull: part.ScheduleFullUpdate(); break; case updatetype.groupterse: group.ScheduleGroupForTerseUpdate(); break; case updatetype.groupfull: group.ScheduleGroupForFullUpdate(); break; default: break; } } } } #endregion internal void SetAxisRotation(int axis, int rotate10) { bool setX = false; bool setY = false; bool setZ = false; int xaxis = 2; int yaxis = 4; int zaxis = 8; setX = ((axis & xaxis) != 0) ? true : false; setY = ((axis & yaxis) != 0) ? true : false; setZ = ((axis & zaxis) != 0) ? true : false; float setval = (rotate10 > 0) ? 1f : 0f; if (setX) RootPart.RotationAxis.X = setval; if (setY) RootPart.RotationAxis.Y = setval; if (setZ) RootPart.RotationAxis.Z = setval; if (setX || setY || setZ) RootPart.SetPhysicsAxisRotation(); } public int registerRotTargetWaypoint(Quaternion target, float tolerance) { scriptRotTarget waypoint = new scriptRotTarget(); waypoint.targetRot = target; waypoint.tolerance = tolerance; uint handle = m_scene.AllocateLocalId(); waypoint.handle = handle; lock (m_rotTargets) { m_rotTargets.Add(handle, waypoint); } m_scene.AddGroupTarget(this); return (int)handle; } public void unregisterRotTargetWaypoint(int handle) { lock (m_targets) { m_rotTargets.Remove((uint)handle); if (m_targets.Count == 0) m_scene.RemoveGroupTarget(this); } } public int registerTargetWaypoint(Vector3 target, float tolerance) { scriptPosTarget waypoint = new scriptPosTarget(); waypoint.targetPos = target; waypoint.tolerance = tolerance; uint handle = m_scene.AllocateLocalId(); waypoint.handle = handle; lock (m_targets) { m_targets.Add(handle, waypoint); } m_scene.AddGroupTarget(this); return (int)handle; } public void unregisterTargetWaypoint(int handle) { lock (m_targets) { m_targets.Remove((uint)handle); if (m_targets.Count == 0) m_scene.RemoveGroupTarget(this); } } public void checkAtTargets() { if (m_scriptListens_atTarget || m_scriptListens_notAtTarget) { if (m_targets.Count > 0) { bool at_target = false; //Vector3 targetPos; //uint targetHandle; Dictionary atTargets = new Dictionary(); lock (m_targets) { foreach (uint idx in m_targets.Keys) { scriptPosTarget target = m_targets[idx]; if (Util.GetDistanceTo(target.targetPos, m_rootPart.GroupPosition) <= target.tolerance) { at_target = true; // trigger at_target if (m_scriptListens_atTarget) { scriptPosTarget att = new scriptPosTarget(); att.targetPos = target.targetPos; att.tolerance = target.tolerance; att.handle = target.handle; atTargets.Add(idx, att); } } } } if (atTargets.Count > 0) { SceneObjectPart[] parts = m_parts.GetArray(); uint[] localids = new uint[parts.Length]; for (int i = 0; i < parts.Length; i++) localids[i] = parts[i].LocalId; for (int ctr = 0; ctr < localids.Length; ctr++) { foreach (uint target in atTargets.Keys) { scriptPosTarget att = atTargets[target]; m_scene.EventManager.TriggerAtTargetEvent( localids[ctr], att.handle, att.targetPos, m_rootPart.GroupPosition); } } return; } if (m_scriptListens_notAtTarget && !at_target) { //trigger not_at_target SceneObjectPart[] parts = m_parts.GetArray(); uint[] localids = new uint[parts.Length]; for (int i = 0; i < parts.Length; i++) localids[i] = parts[i].LocalId; for (int ctr = 0; ctr < localids.Length; ctr++) { m_scene.EventManager.TriggerNotAtTargetEvent(localids[ctr]); } } } } if (m_scriptListens_atRotTarget || m_scriptListens_notAtRotTarget) { if (m_rotTargets.Count > 0) { bool at_Rottarget = false; Dictionary atRotTargets = new Dictionary(); lock (m_rotTargets) { foreach (uint idx in m_rotTargets.Keys) { scriptRotTarget target = m_rotTargets[idx]; double angle = Math.Acos( target.targetRot.X * m_rootPart.RotationOffset.X + target.targetRot.Y * m_rootPart.RotationOffset.Y + target.targetRot.Z * m_rootPart.RotationOffset.Z + target.targetRot.W * m_rootPart.RotationOffset.W) * 2; if (angle < 0) angle = -angle; if (angle > Math.PI) angle = (Math.PI * 2 - angle); if (angle <= target.tolerance) { // trigger at_rot_target if (m_scriptListens_atRotTarget) { at_Rottarget = true; scriptRotTarget att = new scriptRotTarget(); att.targetRot = target.targetRot; att.tolerance = target.tolerance; att.handle = target.handle; atRotTargets.Add(idx, att); } } } } if (atRotTargets.Count > 0) { SceneObjectPart[] parts = m_parts.GetArray(); uint[] localids = new uint[parts.Length]; for (int i = 0; i < parts.Length; i++) localids[i] = parts[i].LocalId; for (int ctr = 0; ctr < localids.Length; ctr++) { foreach (uint target in atRotTargets.Keys) { scriptRotTarget att = atRotTargets[target]; m_scene.EventManager.TriggerAtRotTargetEvent( localids[ctr], att.handle, att.targetRot, m_rootPart.RotationOffset); } } return; } if (m_scriptListens_notAtRotTarget && !at_Rottarget) { //trigger not_at_target SceneObjectPart[] parts = m_parts.GetArray(); uint[] localids = new uint[parts.Length]; for (int i = 0; i < parts.Length; i++) localids[i] = parts[i].LocalId; for (int ctr = 0; ctr < localids.Length; ctr++) { m_scene.EventManager.TriggerNotAtRotTargetEvent(localids[ctr]); } } } } } 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 Vector3 gc = Vector3.Zero; int nparts = m_parts.Count; if (nparts <= 1) return gc; SceneObjectPart[] parts = m_parts.GetArray(); nparts = parts.Length; // just in case it changed if (nparts <= 1) return gc; Quaternion parentRot = RootPart.RotationOffset; Vector3 pPos; // average all parts positions for (int i = 0; i < nparts; i++) { // do it directly // gc += parts[i].GetWorldPosition(); if (parts[i] != RootPart) { pPos = parts[i].OffsetPosition; gc += pPos; } } gc /= nparts; // relative to root: // gc -= AbsolutePosition; return gc; } public float GetMass() { float retmass = 0f; SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) retmass += parts[i].GetMass(); return retmass; } // center of mass of full object public Vector3 GetCenterOfMass() { PhysicsActor pa = RootPart.PhysActor; if(((RootPart.Flags & PrimFlags.Physics) !=0) && pa !=null) { // physics knows better about center of mass of physical prims Vector3 tmp = pa.CenterOfMass; return tmp; } Vector3 Ptot = Vector3.Zero; float totmass = 0f; float m; SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) { m = parts[i].GetMass(); Ptot += parts[i].GetPartCenterOfMass() * m; totmass += m; } if (totmass == 0) totmass = 0; else totmass = 1 / totmass; Ptot *= totmass; return Ptot; } /// /// If the object is a sculpt/mesh, retrieve the mesh data for each part and reinsert it into each 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() { if (IsDeleted) return; if ((RootPart.GetEffectiveObjectFlags() & (uint)PrimFlags.Phantom) != 0) return; // m_log.Debug("Processing CheckSculptAndLoad for {0} {1}", Name, LocalId); SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) parts[i].CheckSculptAndLoad(); } /// /// Set the user group to which this scene object belongs. /// /// /// public void SetGroup(UUID GroupID, IClientAPI client) { SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) { SceneObjectPart part = parts[i]; part.SetGroup(GroupID, client); part.Inventory.ChangeInventoryGroup(GroupID); } HasGroupChanged = true; // Don't trigger the update here - otherwise some client issues occur when multiple updates are scheduled // for the same object with very different properties. The caller must schedule the update. //ScheduleGroupForFullUpdate(); } public void TriggerScriptChangedEvent(Changed val) { SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) parts[i].TriggerScriptChangedEvent(val); } /// /// Returns a count of the number of scripts in this groups parts. /// public int ScriptCount() { int count = 0; SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) count += parts[i].Inventory.ScriptCount(); return count; } /// /// A float the value is a representative execution time in milliseconds of all scripts in the link set. /// public float ScriptExecutionTime() { IScriptModule[] engines = Scene.RequestModuleInterfaces(); if (engines.Length == 0) // No engine at all return 0.0f; float time = 0.0f; // get all the scripts in all parts SceneObjectPart[] parts = m_parts.GetArray(); List scripts = new List(); for (int i = 0; i < parts.Length; i++) { scripts.AddRange(parts[i].Inventory.GetInventoryItems(InventoryType.LSL)); } // extract the UUIDs List ids = new List(scripts.Count); foreach (TaskInventoryItem script in scripts) { if (!ids.Contains(script.ItemID)) { ids.Add(script.ItemID); } } // Offer the list of script UUIDs to each engine found and accumulate the time foreach (IScriptModule e in engines) { if (e != null) { time += e.GetScriptExecutionTime(ids); } } return time; } /// /// Returns a count of the number of running scripts in this groups parts. /// public int RunningScriptCount() { int count = 0; SceneObjectPart[] parts = m_parts.GetArray(); for (int i = 0; i < parts.Length; i++) count += parts[i].Inventory.RunningScriptCount(); return count; } public override string ToString() { return String.Format("{0} {1} ({2})", Name, UUID, AbsolutePosition); } #region ISceneObject public virtual ISceneObject CloneForNewScene() { SceneObjectGroup sog = Copy(false); sog.IsDeleted = false; return sog; } public virtual string ToXml2() { return SceneObjectSerializer.ToXml2Format(this); } public virtual string ExtraToXmlString() { return "" + FromItemID.ToString() + ""; } public virtual void ExtraFromXmlString(string xmlstr) { string id = xmlstr.Substring(xmlstr.IndexOf("")); id = xmlstr.Replace("", ""); id = id.Replace("", ""); UUID uuid = UUID.Zero; UUID.TryParse(id, out uuid); FromItemID = uuid; } public void ResetOwnerChangeFlag() { ForEachPart(delegate(SceneObjectPart part) { part.ResetOwnerChangeFlag(); }); } #endregion } }