diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs
index fc0476157f..52469a2302 100644
--- a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs
+++ b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs
@@ -451,6 +451,8 @@ namespace OpenSim.Region.Framework.Scenes
}
}
+ // Restuff the new GroupPosition into each SOP of the linkset.
+ // This has the affect of resetting and tainting the physics actors.
SceneObjectPart[] parts = m_parts.GetArray();
for (int i = 0; i < parts.Length; i++)
parts[i].GroupPosition = val;
@@ -1133,6 +1135,9 @@ namespace OpenSim.Region.Framework.Scenes
public void ResetChildPrimPhysicsPositions()
{
+ // Setting this SOG's absolute position also loops through and sets the positions
+ // of the SOP's in this SOG's linkset. This has the side affect of making sure
+ // the physics world matches the simulated world.
AbsolutePosition = AbsolutePosition; // could someone in the know please explain how this works?
// teravus: AbsolutePosition is NOT a normal property!
@@ -1987,6 +1992,8 @@ namespace OpenSim.Region.Framework.Scenes
LinkToGroup(objectGroup, false);
}
+ // Link an existing group to this group.
+ // The group being linked need not be a linkset -- it can have just one prim.
public void LinkToGroup(SceneObjectGroup objectGroup, bool insert)
{
// m_log.DebugFormat(
@@ -1997,35 +2004,51 @@ namespace OpenSim.Region.Framework.Scenes
if (objectGroup == this)
return;
+ // 'linkPart' == the root of the group being linked into this group
SceneObjectPart linkPart = objectGroup.m_rootPart;
// physics flags from group to be applied to linked parts
bool grpusephys = UsesPhysics;
bool grptemporary = IsTemporary;
+ // Remember where the group being linked thought it was
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;
+ // A linked SOP remembers its location and rotation relative to the root of a group.
+ // Convert the root of the group being linked to be relative to the
+ // root of the group being linked to.
+ // Note: Some of the assignments have complex side effects.
+ // First move the new group's root SOP's position to be relative to ours
+ // (radams1: Not sure if the multiple setting of OffsetPosition is required. If not,
+ // this code can be reordered to have a more logical flow.)
+ linkPart.OffsetPosition = linkPart.GroupPosition - AbsolutePosition;
+ // Assign the new parent to the root of the old group
+ linkPart.ParentID = m_rootPart.LocalId;
+ // Now that it's a child, it's group position is our root position
+ linkPart.GroupPosition = AbsolutePosition;
+
+ Vector3 axPos = linkPart.OffsetPosition;
+ // Rotate the linking root SOP's position to be relative to the new root prim
Quaternion parentRot = m_rootPart.RotationOffset;
axPos *= Quaternion.Inverse(parentRot);
-
linkPart.OffsetPosition = axPos;
+
+ // Make the linking root SOP's rotation relative to the new root prim
Quaternion oldRot = linkPart.RotationOffset;
Quaternion newRot = Quaternion.Inverse(parentRot) * oldRot;
linkPart.RotationOffset = newRot;
- linkPart.ParentID = m_rootPart.LocalId;
-
+ // If there is only one SOP in a SOG, the LinkNum is zero. I.e., not a linkset.
+ // Now that we know this SOG has at least two SOPs in it, the new root
+ // SOP becomes the first in the linkset.
if (m_rootPart.LinkNum == 0)
m_rootPart.LinkNum = 1;
lock (m_parts.SyncRoot)
{
+ // Calculate the new link number for the old root SOP
int linkNum;
if (insert)
{
@@ -2041,6 +2064,7 @@ namespace OpenSim.Region.Framework.Scenes
linkNum = PrimCount + 1;
}
+ // Add the old root SOP as a part in our group's list
m_parts.Add(linkPart.UUID, linkPart);
linkPart.SetParent(this);
@@ -2048,6 +2072,8 @@ namespace OpenSim.Region.Framework.Scenes
// 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);
+
+ // If the added SOP is physical, also tell the physics engine about the link relationship.
if (linkPart.PhysActor != null && m_rootPart.PhysActor != null && m_rootPart.PhysActor.IsPhysical)
{
linkPart.PhysActor.link(m_rootPart.PhysActor);
@@ -2056,20 +2082,26 @@ namespace OpenSim.Region.Framework.Scenes
linkPart.LinkNum = linkNum++;
+ // Get a list of the SOP's in the old group in order of their linknum's.
SceneObjectPart[] ogParts = objectGroup.Parts;
Array.Sort(ogParts, delegate(SceneObjectPart a, SceneObjectPart b)
{
return a.LinkNum - b.LinkNum;
});
+ // Add each of the SOP's from the old linkset to our linkset
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
+
+ // Update the physics flags for the newly added SOP
+ // (Is this necessary? LinkNonRootPart() has already called UpdatePrimFlags but with different flags!??)
part.UpdatePrimFlags(grpusephys, grptemporary, (IsPhantom || (part.Flags & PrimFlags.Phantom) != 0), part.VolumeDetectActive);
+
+ // If the added SOP is physical, also tell the physics engine about the link relationship.
if (part.PhysActor != null && m_rootPart.PhysActor != null && m_rootPart.PhysActor.IsPhysical)
{
part.PhysActor.link(m_rootPart.PhysActor);
@@ -2080,6 +2112,7 @@ namespace OpenSim.Region.Framework.Scenes
}
}
+ // Now that we've aquired all of the old SOG's parts, remove the old SOG from the scene.
m_scene.UnlinkSceneObject(objectGroup, true);
objectGroup.IsDeleted = true;
@@ -2152,7 +2185,7 @@ namespace OpenSim.Region.Framework.Scenes
///
/// 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.
+ /// alternative method that does take a lock to delink a single prim.
///
///
///
@@ -2165,6 +2198,7 @@ namespace OpenSim.Region.Framework.Scenes
linkPart.ClearUndoState();
+ Vector3 worldPos = linkPart.GetWorldPosition();
Quaternion worldRot = linkPart.GetWorldRotation();
// Remove the part from this object
@@ -2174,6 +2208,7 @@ namespace OpenSim.Region.Framework.Scenes
SceneObjectPart[] parts = m_parts.GetArray();
+ // Rejigger the linknum's of the remaining SOP's to fill any gap
if (parts.Length == 1 && RootPart != null)
{
// Single prim left
@@ -2195,22 +2230,31 @@ namespace OpenSim.Region.Framework.Scenes
PhysicsActor linkPartPa = linkPart.PhysActor;
+ // Remove the SOP from the physical scene.
+ // If the new SOG is physical, it is re-created later.
+ // (There is a problem here in that we have not yet told the physics
+ // engine about the delink. Someday, linksets should be made first
+ // class objects in the physics engine interface).
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
+
+ /* This commented out code seems to recompute what GetWorldPosition already does.
+ * Replace with a call to GetWorldPosition (before unlinking)
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.GroupPosition = worldPos;
+ linkPart.OffsetPosition = Vector3.Zero;
linkPart.RotationOffset = worldRot;
+ // Create a new SOG to go around this unlinked and unattached SOP
SceneObjectGroup objectGroup = new SceneObjectGroup(linkPart);
m_scene.AddNewSceneObject(objectGroup, true);
@@ -2239,42 +2283,56 @@ namespace OpenSim.Region.Framework.Scenes
m_isBackedUp = false;
}
+ // This links an SOP from a previous linkset into my linkset.
+ // The trick is that the SOP's position and rotation are relative to the old root SOP's
+ // so we are passed in the position and rotation of the old linkset so this can
+ // unjigger this SOP's position and rotation from the previous linkset and
+ // then make them relative to my linkset root.
private void LinkNonRootPart(SceneObjectPart part, Vector3 oldGroupPosition, Quaternion oldGroupRotation, int linkNum)
{
Quaternion parentRot = oldGroupRotation;
Quaternion oldRot = part.RotationOffset;
- Quaternion worldRot = parentRot * oldRot;
-
- parentRot = oldGroupRotation;
+ // Move our position to not be relative to the old parent
Vector3 axPos = part.OffsetPosition;
-
axPos *= parentRot;
part.OffsetPosition = axPos;
part.GroupPosition = oldGroupPosition + part.OffsetPosition;
part.OffsetPosition = Vector3.Zero;
+
+ // Compution our rotation to be not relative to the old parent
+ Quaternion worldRot = parentRot * oldRot;
part.RotationOffset = worldRot;
+ // Add this SOP to our linkset
part.SetParent(this);
part.ParentID = m_rootPart.LocalId;
-
m_parts.Add(part.UUID, part);
part.LinkNum = linkNum;
+ // Compute the new position of this SOP relative to the group position
part.OffsetPosition = part.GroupPosition - AbsolutePosition;
- Quaternion rootRotation = m_rootPart.RotationOffset;
+ // (radams1 20120711: I don't know why part.OffsetPosition is set multiple times.
+ // It would have the affect of setting the physics engine position multiple
+ // times. In theory, that is not necessary but I don't have a good linkset
+ // test to know that cleaning up this code wouldn't break things.)
+ // Rotate the relative position by the rotation of the group
+ Quaternion rootRotation = m_rootPart.RotationOffset;
Vector3 pos = part.OffsetPosition;
pos *= Quaternion.Inverse(rootRotation);
part.OffsetPosition = pos;
+ // Compute the SOP's rotation relative to the rotation of the group.
parentRot = m_rootPart.RotationOffset;
oldRot = part.RotationOffset;
Quaternion newRot = Quaternion.Inverse(parentRot) * oldRot;
part.RotationOffset = newRot;
+ // Since this SOP's state has changed, push those changes into the physics engine
+ // and the simulator.
part.UpdatePrimFlags(UsesPhysics, IsTemporary, IsPhantom, IsVolumeDetect);
}
diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs
index b3f11a756e..4b2fedead8 100644
--- a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs
+++ b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs
@@ -693,9 +693,11 @@ namespace OpenSim.Region.Framework.Scenes
{
// If this is a linkset, we don't want the physics engine mucking up our group position here.
PhysicsActor actor = PhysActor;
+ // If physical and the root prim of a linkset, the position of the group is what physics thinks.
if (actor != null && ParentID == 0)
m_groupPosition = actor.Position;
+ // If I'm an attachment, my position is reported as the position of who I'm attached to
if (ParentGroup.IsAttachment)
{
ScenePresence sp = ParentGroup.Scene.GetScenePresence(ParentGroup.AttachedAvatar);
@@ -721,7 +723,7 @@ namespace OpenSim.Region.Framework.Scenes
}
else
{
- // To move the child prim in respect to the group position and rotation we have to calculate
+ // The physics engine always sees all objects (root or linked) in world coordinates.
actor.Position = GetWorldPosition();
actor.Orientation = GetWorldRotation();
}
@@ -795,6 +797,8 @@ namespace OpenSim.Region.Framework.Scenes
{
// We don't want the physics engine mucking up the rotations in a linkset
PhysicsActor actor = PhysActor;
+ // If this is a root of a linkset, the real rotation is what the physics engine thinks.
+ // If not a root prim, the offset rotation is computed by SOG and is relative to the root.
if (ParentID == 0 && (Shape.PCode != 9 || Shape.State == 0) && actor != null)
{
if (actor.Orientation.X != 0f || actor.Orientation.Y != 0f
@@ -1980,14 +1984,20 @@ namespace OpenSim.Region.Framework.Scenes
/// A Linked Child Prim objects position in world
public Vector3 GetWorldPosition()
{
- Quaternion parentRot = ParentGroup.RootPart.RotationOffset;
- Vector3 axPos = OffsetPosition;
- axPos *= parentRot;
- Vector3 translationOffsetPosition = axPos;
- if(_parentID == 0)
- return GroupPosition;
+ Vector3 ret;
+ if (_parentID == 0)
+ // if a root SOP, my position is what it is
+ ret = GroupPosition;
else
- return ParentGroup.AbsolutePosition + translationOffsetPosition;
+ {
+ // If a child SOP, my position is relative to the root SOP so take
+ // my info and add the root's position and rotation to
+ // get my world position.
+ Quaternion parentRot = ParentGroup.RootPart.RotationOffset;
+ Vector3 translationOffsetPosition = OffsetPosition * parentRot;
+ ret = ParentGroup.AbsolutePosition + translationOffsetPosition;
+ }
+ return ret;
}
///
@@ -2004,6 +2014,8 @@ namespace OpenSim.Region.Framework.Scenes
}
else
{
+ // A child SOP's rotation is relative to the root SOP's rotation.
+ // Combine them to get my absolute rotation.
Quaternion parentRot = ParentGroup.RootPart.RotationOffset;
Quaternion oldRot = RotationOffset;
newRot = parentRot * oldRot;