The easiest way to do what you want is to align your objects to an unrotated root primitive. This way, when your script actually uses the coordinates you get from the initial alignment, it doesnt have to compensate for what the rotation of the root prim was at the time of alignment.
First, set the rotation of the primitive you will use as your "root" to <0, 0, 0> (ZERO_ROTATION). Then position the unlinked objects where you want them to be.
Record the quaternion rotation of each object in your set (from llGetRot), and the offset from the root to the object (objectPosition - rootPosition). Each object in your set is responsible for knowing its own rotation and offset, the way you do it is up to you; you can hardcode it in each object of the set or even have the root somehow distribute the info.
The set objects must know the position and rotation of the root at all times. The most reliable way of doing this, at least since last I experimented, is via listeners. When the root is moved or rotated, it should broadcast its position and rotation on a private chat channel that the set objects listen to. This method has several advantages over using a sensor: it wont drop out when the root goes over a sim border, and it will only broadcast a root update when the root moves. When you use the listener method, however, you need to compensate for simulator-relative coordinate's inconsistancies.
llGetPos returns the simulator-relative position of the root. If the root only broadcasts that information, when it passes into another simulator, objects in the set that are not in the same simulator may face an ambiguity.
For example, if the root object moves from Simulator A, <256, 256, 50> into Simulator B <0, 0, 50>, and broadcasts <0, 0, 50>, set objects that are not within Simulator B will move to Simulator A's <0, 0, 50>, nearly 362 meters away, outside of chat range. To solve this problem, it is necessesary to use global coordinates. The wiki has a nice page on them, but I think its still down. Basically, the root not only has to broadcast the position it gets from llGetPos(), but it also has to broadcast what it gets from llGetRegionCorner().
In each set object, the script must recieve the three pieces of information from the root, the root's region corner, the root's position, and the root's rotation, and calculate its orientation based on that.
I posted this neat little function that will do the positioning for you in the script library a while ago:
vector getLocalVec(vector localCoords, vector regionCorner)
{
vector simOffset = regionCorner - llGetRegionCorner();
return simOffset + localCoords;
}
This will convert the root's position and region corner into a position relative to the simulator the set object is in.
As for the actual math, here's some sample code:
// This code is in each set object.
// This variable contains the hardcoded offset of this object from the root.
vector MY_OFFSET;
// This variable contains the hardcoded rotation of the object.
rotation MY_ROT;
vector getLocalVec(vector localCoords, vector regionCorner)
{
vector simOffset = regionCorner - llGetRegionCorner();
return simOffset + localCoords;
}
listen(integer channel, string name, key id, string message) {
// Do stuff to fill these variables with their info:
vector rootRegion;
vector rootPosition;
rotation rootRotation;
// The root's position local to the simulator this object is in.
vector localRootPosition = getLocalVec(rootPosition, rootRegion);
// This rotates this object's position and rotation based on the root's rotation.
vector rootRotatedOffset = MY_OFFSET * rootRotation;
rotation rootRotatedRot = MY_ROT * rootRotation;
// Now pass the values to functions that orient this object:
llSetPos(rootRotatedOffset + localRotPosition);
llSetRot(rootRotatedRot);
}
Ive left some things up to you, namely the code required to parse the info passed from the root to the set object, and the set object's hardcoded positional data.
I hope this helps!
==Chris