ReDragon2013 Posted May 22, 2021 Share Posted May 22, 2021 (edited) Thank you, dafydd99, for your papyrus code related to rotation matrix. In this context I want to reflect to wiki page https://www.creationkit.com/index.php?title=Spatial_functions_and_snippets, which is a mirror from an old bethesda forum posting made by chesko. GetPosXYZRotateAroundRef() ;-----------\ ;Description \ Author: Chesko ;---------------------------------------------------------------- ; https://www.creationkit.com/index.php?title=Spatial_functions_and_snippets ;Rotates a point (akObject offset from the center of ;rotation (akOrigin) by the supplied degrees fAngleX, fAngleY, ;fAngleZ, and returns the new position of the point. ;-------------\ ;Return Values \ ;---------------------------------------------------------------- ; fNewPos[0] = The new X position of the point ; fNewPos[1] = The new Y position of the point ; fNewPos[2] = The new Z position of the point ; | 1 0 0 | ;Rx(t) = | 0 cos(t) -sin(t) | ; | 0 sin(t) cos(t) | ; ; | cos(t) 0 sin(t) | ;Ry(t) = | 0 1 0 | ; |-sin(t) 0 cos(t) | ; ; | cos(t) -sin(t) 0 | ;Rz(t) = | sin(t) cos(t) 0 | ; | 0 0 1 | ;R * v = Rv, where R = rotation matrix, v = column vector of point [ x y z ], Rv = column vector of point after rotation ;Provided angles must follow Bethesdas conventions (CW Z angle for example). float[] function GetPosXYZRotateAroundRef(ObjectReference akOrigin, ObjectReference akObject, float fAngleX, float fAngleY, float fAngleZ) fAngleX = -(fAngleX) fAngleY = -(fAngleY) fAngleZ = -(fAngleZ) float myOriginPosX = akOrigin.GetPositionX() float myOriginPosY = akOrigin.GetPositionY() float myOriginPosZ = akOrigin.GetPositionZ() float fInitialX = akObject.GetPositionX() - myOriginPosX float fInitialY = akObject.GetPositionY() - myOriginPosY float fInitialZ = akObject.GetPositionZ() - myOriginPosZ float fNewX float fNewY float fNewZ ;Objects in Skyrim are rotated in order of Z, Y, X, so we will do that here as well. ;Z-axis rotation matrix float fVectorX = fInitialX float fVectorY = fInitialY float fVectorZ = fInitialZ fNewX = (fVectorX * cos(fAngleZ)) + (fVectorY * sin(-fAngleZ)) + (fVectorZ * 0) fNewY = (fVectorX * sin(fAngleZ)) + (fVectorY * cos(fAngleZ)) + (fVectorZ * 0) fNewZ = (fVectorX * 0) + (fVectorY * 0) + (fVectorZ * 1) ;Y-axis rotation matrix fVectorX = fNewX fVectorY = fNewY fVectorZ = fNewZ fNewX = (fVectorX * cos(fAngleY)) + (fVectorY * 0) + (fVectorZ * sin(fAngleY)) fNewY = (fVectorX * 0) + (fVectorY * 1) + (fVectorZ * 0) fNewZ = (fVectorX * sin(-fAngleY)) + (fVectorY * 0) + (fVectorZ * cos(fAngleY)) ;X-axis rotation matrix fVectorX = fNewX fVectorY = fNewY fVectorZ = fNewZ fNewX = (fVectorX * 1) + (fVectorY * 0) + (fVectorZ * 0) fNewY = (fVectorX * 0) + (fVectorY * cos(fAngleX)) + (fVectorZ * sin(-fAngleX)) fNewZ = (fVectorX * 0) + (fVectorY * sin(fAngleX)) + (fVectorZ * cos(fAngleX)) ;Return result float[] fNewPos = new float[3] fNewPos[0] = fNewX + myOriginPosX fNewPos[1] = fNewY + myOriginPosY fNewPos[2] = fNewZ + myOriginPosZ return fNewPos endFunction And as second hint, it is possible to use an array (of any type) as function parameter. You do not have to initialize the array every time and return it back to caller function. My changes of your code as follow: Utility_Rotation Scriptname Utility_Rotation Hidden ; https://forums.nexusmods.com/index.php?/topic/9011983-local-to-global-rotations-that-old-chestnut/page-2 ; original functions made by dafydd99 ; -- FUNCTIONs -- 2 + (1) ; How to call? ; float f = Utility_Rotation.atan2(fAngleY, fAngleX) ; Utility_Rotation.zAxisRotate(objectRef, fRotationAngleZ) ;--------------------------------------- Float FUNCTION atan2(Float aY, Float aX) Global ; public function to returning degrees ;--------------------------------------- IF ( aX ) ; (aX != 0.0) ELSE IF (aY > 0) RETURN 90.0 ; x=0, y>0 ENDIF IF (aY < 0) RETURN -90.0 ; x=0, y<0 ENDIF RETURN 0.0 ; x=0, y=0 undefined ENDIF ;------------- float f = Math.ATAN(aY/aX) IF (aX > 0) RETURN f ; x>0, y ENDIF ;--------- IF (aY < 0) f = f - 180.0 ; x<0, y<0 ELSE f = f + 180.0 ; x<0, y>=0 ENDIF RETURN f ENDFUNCTION ;---------------------------------------------------- FUNCTION zAxisRotate(ObjectReference oRef, Float aZR) Global ; public function ;---------------------------------------------------- float[] a = new Float[3] ; XYZ, aZR = angleZRotate IF (oRef) && oRef.GetBaseObject() a[0] = oRef.GetAngleX() ; originalXAngle a[1] = oRef.GetAngleY() ; originalYAngle a[2] = oRef.GetAngleZ() ; originalZAngle ELSE Debug.Trace(" zAxisRotate() - Error: Invalid object detected!") RETURN ; - STOP - invalid Ref detected! ENDIF ;-------------------- Calculate(a, -a[2]) ; fill in normalisedAngles Calculate(a, (aZR - a[2])) ; fill in angles oRef.SetAngle(a[0], a[1], a[2]) ENDFUNCTION ;-------------------------------------- FUNCTION Calculate(Float[] a, Float aZ) Global ; private ;-------------------------------------- ; Keep in mind: parameter "Float[] a" is a pointer to array! float aX = a[0] float aY = a[1] aX = -aX ; localAngX aY = -aY ; localAngY aZ = -aZ ; localAngZ ; store the "sine" and "cosine" of our local angles ; ------------------------------------------------- float sx = Math.SIN(aX) float sy = Math.SIN(aY) float sz = Math.SIN(aZ) float cx = Math.COS(aX) float cy = Math.COS(aY) float cz = Math.COS(aZ) ; roation matrix ZYX ; ------------------ ;;; float r11 = cz*cy ;;; float r12 = -sz*cx + (sy*sx*cz) ;;; float r13 = sz*sx + (sy*cx*cz) ;;; float r21 = sz*cy ;;; float r22 = cz*cx + (sy*sx*sz) ;;; float r23 = -sx*cz + (sy*cx*sz) ;;; float r31 = -sy ;;; float r32 = cy*sx ;;; float r33 = cy*cx ; extract the worldangles from rotation matrix above ; -------------------------------------------------- aY = sx*sz + (sy*cx*cz) ; r13 IF (aY < -0.9998) ; negative gimbal lock a[1] = 90.0 a[2] = 0.0 aY = sx*cy ; r32 aX = cx*cz + (sx*sy*sz) ; r22 ELSEIF (aY > 0.9998) ; positive gimbal lock a[1] = -90.0 a[2] = 0.0 aY = sx*cy ; r32 aX = cx*cz + (sx*sy*sz) ; r22 ELSE ; no gimbal lock aY = Math.ASIN(aY) ; asin(r13) a[1] = -aY aZ = -sz*cx + (sx*sy*cz) ; r12 aZ = atan2(-aZ, cy*cz) ; (-r12, r11) a[2] = -aZ aY = -sx*cz + (cx*sy*sz) aY = -aY ;-r23 aX = cx*cy ; r33 ENDIF a[0] = -atan2(aY,aX) ENDFUNCTION Edited May 22, 2021 by ReDragon2013 Link to comment Share on other sites More sharing options...
dafydd99 Posted May 22, 2021 Author Share Posted May 22, 2021 Thanks ReDragon2013 - my code had been stitched together from several other postings that I believe have already been referenced and put into a more generic form. The real breakthrough for me was a realisation that it's necessary to 'undo' the rotations (normalise) before re-applying newly calculated ones to get to the correct orientation - there must be an easier solution that doesn't require this second stage, but 3D orientation stuff is surprisingly complex, and my maths is a long way from good enough to solve it! Happy to recommend people use either your updated version or my old one depending on their code style and formatting preferences. The Chesko code is very useful and I've needed it myself - but just to be clear it solves a very different problem, which is the position of a reference rotated around another reference. So, for example if you wanted a untextured sphere to rotate around a pillar, it would work fine as is. If, however you wanted to rotate a skull (specifically one which was offset in the X,Y rotations - eg to look 'down' on the player) it would also require these functions to be oriented correctly. It has been frustrating that Bethesda didn't quite manage a full set of built in 3D operations - if you want an example of how incomplete they are, you can try doing something like a slow TranslateToRef to rotate an object reference at a non-zero rotation, and in certain situations it'll rotate to a totally different angle, then take a 90deg change of direction, before finally rotating to the destination position. I tried this with opening/closing a toilet lid and never found a decent resolution! Cheers - dafydd99 Link to comment Share on other sites More sharing options...
dylbill Posted May 23, 2021 Share Posted May 23, 2021 So to expand upon this even further, currently I'm trying to script an object to translate to another object, while always facing that object. Problem is the object I'm translating to could change position. I remembered some functions I wrote for my mod Turn Objects Up for Fallout4 that helps for this. This function will get the difference in the CenterRef's heading angle from the OrbitRef's position. Static Property XMarker Auto Float Function GetAngleOfRefFromHeading(ObjectReference akCenterRef, ObjectReference OrbitRef) ;Get the angle difference of the CenterRef's heading (z angle) and the orbit ref. ;So if the orbit ref is directly behind the center ref, it will return 180 ;Used to make the CenterRef face the OrbitRef ObjectReference CenterRef = akCenterRef.PlaceAtMe(XMarker, 1) Float CurrentDistance = GetDistance2D(OrbitRef, CenterRef) Float CenterAngleZ = CenterRef.GetAngleZ() ObjectReference ZeroRef = CenterRef.PlaceAtMe(XMarker, 1, false, true) ObjectReference RightRef = CenterRef.PlaceAtMe(XMarker, 1, false, true) ObjectReference LeftRef = CenterRef.PlaceAtMe(XMarker, 1, false, true) Float XDist = Math.Sin(CenterAngleZ) Float YDist = math.Cos(CenterAngleZ) XDist *= CurrentDistance YDist *= CurrentDistance ZeroRef.MoveTo(CenterRef, XDist, YDist, OrbitRef.GetPositionZ()) XDist = Math.Sin(CenterAngleZ + 90) YDist = math.Cos(CenterAngleZ + 90) XDist *= CurrentDistance YDist *= CurrentDistance RightRef.MoveTo(CenterRef, XDist, YDist, OrbitRef.GetPositionZ()) XDist = Math.Sin(CenterAngleZ - 90) YDist = math.Cos(CenterAngleZ - 90) XDist *= CurrentDistance YDist *= CurrentDistance LeftRef.MoveTo(CenterRef, XDist, YDist, OrbitRef.GetPositionZ()) If GetDistance2D(OrbitRef, RightRef) < GetDistance2D(OrbitRef, LeftRef) Float ADistance = GetDistance2D(CenterRef, ZeroRef) Float BDistance = GetDistance2D(ZeroRef, OrbitRef) Float CDistance = GetDistance2D(OrbitRef, CenterRef) Float CurrentAngleA = Math.acos( ((BDistance * BDistance) - (ADistance * ADistance) - (CDistance * CDistance)) / (2 * ADistance * CDistance) ) Float AngleDiff = (180 - CurrentAngleA) CenterRef.Delete() ZeroRef.Delete() RightRef.Delete() LeftRef.Delete() Return AngleDiff Else Float ADistance = GetDistance2D(CenterRef, ZeroRef) Float BDistance = GetDistance2D(ZeroRef, OrbitRef) Float CDistance = GetDistance2D(OrbitRef, CenterRef) Float CurrentAngleA = Math.acos( ((BDistance * BDistance) - (ADistance * ADistance) - (CDistance * CDistance)) / (2 * ADistance * CDistance) ) Float AngleDiff = (180 + CurrentAngleA) CenterRef.Delete() ZeroRef.Delete() RightRef.Delete() LeftRef.Delete() Return AngleDiff Endif EndFunction Float Function GetDistance2D(ObjectReference RefA, ObjectReference RefB) Float RefAX = RefA.GetPositionX() Float RefAY = RefA.GetPositionY() Float RefBX = RefB.GetPositionX() Float RefBY = RefB.GetPositionY() Float Distance = Math.sqrt( ((RefAX-RefBX) * (RefAX-RefBX)) + ((RefAY-RefBY) * (RefAY-RefBY)) ) Return Distance EndFunction I tested it with this function to make the player face an object. Float AngleDiff = GetAngleOfRefFromHeading(PlayerRef, ConsoleUtil.GetSelectedReference()) PlayerRef.SetAngle(PlayerRef.GetAngleZ(), PlayerRef.GetAngleY(), PlayerRef.GetAngleZ() + AngleDiff) It worked like a charm. No matter where I was around the object, or which direction I was facing, it would make me face the object. And if you need to keep relative X and Y angles, you can use your function zAxisRotate(PlayerRef, PlayerRef.GetAngleZ() + AngleDiff) Link to comment Share on other sites More sharing options...
AnishaDawn Posted May 23, 2021 Share Posted May 23, 2021 Either it's jealousy or self admitting the truth, either way this thread has shown me that no matter how hard I try or how much reading I do, I'll never be smart enough to fit in around here. Link to comment Share on other sites More sharing options...
maxarturo Posted May 23, 2021 Share Posted May 23, 2021 (edited) I've been watching this threat to observe the different point of views cause its quite interesting. Although all this can be done easier if you do it on the nif level, but that's another thing... @ AnishaDawn This is not a "Smart's" competition and all opinions and point of views are welcome from every individuals modding level. You don't need to be smart enough or the smarter of all to fit in here, the only required thing to fit in is to have the unconditional desire to help a fellow modder by sharing knowledge, knowhow and occationaly creating something for them to help them make their ideas into reality. * The only way to create better future modders and push forwards the gaming industry is to willingly educate and learn from each other. * Like it or not modders have shaped the gaming scene as it is today, every single innovation and leap forwards was done by modders which the industry took advantage and in some cases even stole. Edited May 23, 2021 by maxarturo Link to comment Share on other sites More sharing options...
dafydd99 Posted May 23, 2021 Author Share Posted May 23, 2021 @dylbill - looks good! Glad you're finding more usages for this. One tiny typo might be in calling this function - PlayerRef.SetAngle(PlayerRef.GetAngleZ().... - that should probably be GetAngleX()? @AnishaDawn - I very much agree with Maxaturo here - there's so much that can be done with a mod, being good at everything is next to impossible. Even the experts who worked on Skyrim would likely not be able to do everything. And the area we're looking at here, 3D maths is hard - super hard. Like maths degree level hard. 2D maths can be tricky, but 3D is another level beyond, and as such you'd usually have all the stuff needed built into the scripting language libraries - but even Bethesda couldn't manage that completely. It took months for me to get close to something that works from various examples I found online and a lot of trial and error and I still don't really understand it! Just glad it works - lol! @maxaturo - re nifs, yes you're quite right, but it does depend on the application, and in some 'attach ref' might do the trick. This is hopefully the 'general' scripting solution for the problem, and as such can help in situations where creating a new nif everytime might not be ideal. Eg as a modder I'd prefer to be able to reposition/rotate references in the creation kit than change the nif that represents them. Link to comment Share on other sites More sharing options...
dylbill Posted May 23, 2021 Share Posted May 23, 2021 @dafydd99 Yes, nice catch. That was a type, it should be GetAngleX() @AnishaDawn, I agree with Maxaturo and Dafdd99 as well. The functions I posted I had to google a lot to learn how to do, then I also had to do a bunch of trial and error to make them work in Skyrim. As Maxaturo said, all view points are welcome and will help modders make what they want to make. @maxaturo, agree most of the time setting things up directly in the nif is easier, but as dafydd99 said, it depends on the application. For my case, translating and object to a moving object and keeping it facing the object can't really be done in a nif, so it's good to know these papyrus functions when using only a nif isn't possible. Link to comment Share on other sites More sharing options...
Recommended Posts