Jump to content

Local to global rotations - that old chestnut!


dafydd99

Recommended Posts

This one has been bugging me for a year, and I'm yet to solve it or find a solution online.

 

The problem is simple. If, for example, we were to have a giant skull sat on the ground with rotations of x=0,y=0,z=0, and say we wanted it to rotate on the spot. Fine - you just change the z to another value - say 30, and it would rotate 30 degrees. The trouble comes when, for example, you've changed the x-axis rotation to 20 degrees, perhaps to make the skull lean forward menacingly. Because the angles are Global, not Local, as you rotate it around the z-axis you also have to adjust the x-axis rotation and the y-axis rotation too - if you don't change it, the skull ends up at a jaunty angle - not nearly menacing enough, and frankly might make the player laugh rather than run away in fear.

 

Fortunately bethesda have provided a function to do exactly this on their creation kit page... https://www.creationkit.com/index.php?title=Setting_Local_Rotation

Function SetLocalAngle(Float LocalX, Float LocalY, Float LocalZ)
	float AngleX = LocalX * Math.Cos(LocalZ) + LocalY * Math.Sin(LocalZ)
	float AngleY = LocalY * Math.Cos(LocalZ) - LocalX * Math.Sin(LocalZ)
	SetAngle(AngleX, AngleY, LocalZ)
EndFunction

But.... it doesn't WORK!!! For anything but 90 degree rotations it's off.

 

Does anyone have the maths to fix it or could point me to a function that does what I need? It seems like such a fundamental thing to need in a game - so I must be missing something...

 

Thanks to anyone who can help,

 

Cheers - dafydd99

Link to comment
Share on other sites

https://www.creationkit.com/index.php?title=SetAngle_-_ObjectReference

 

Maybe the notes provided by a rather talented modder on the Set Angle page can help you fix it yourself, I think. I'm not good with trig at all myself.

 

Also not sure if this well help but it's another example of rotation scripting:

 

https://www.creationkit.com/index.php?title=Spatial_functions_and_snippets

Edited by Rasikko
Link to comment
Share on other sites

Thanks Rasikko! I really appreciate your reply!

 

Yeah the SetAngle page explains more about how angles are used in the game but not how to solve my problem unfortunately.

 

The other page is a very useful rotation function which I actually already use, but it's about rotating an object around another to find it's new position - it doesn't deal with the X,Y,Z rotations of an object (ie direction the object is facing) which is what I'm after.

 

Yes, I don't remember enough maths to work it out myself - and whenever I look for a solution online, it seems to be people saying, "oh, just apply this matrix" - without defining what it is, as it's a coded resource on a platform (eg unity) - or talking about quaternions, which is beyond any maths I've done.

 

So - still stuck. :confused:

 

Thanks again - dafydd99

Link to comment
Share on other sites

Okay - spent the whole weekend looking at this and still no joy. I think I'm understanding the problem a little better anyhow - local rotations may be barking up the wrong tree.

 

Creation kit uses Global rotations or 'world' rotations - ie they're fixed to the world, not the object being rotated. If you rotate around a global axis this can affect the other two rotation angles. You can see this in the creation kit - place an object, say a giant skull as above, in a cell, and maybe make it lean in a little (by altering the X or Y angles). If you rotate it by holding Z and moving the mouse with the right button pressed, the 'Z' angle will be changed. However, you'll also notice the other two angles (X and Y) will change too. These changes are what I want to mimic in a script. This would be fine if through scripting you could specify just one angle to change, eg SetAngleZ(newZAngle) as the engine could then recalculate the others, but instead we only have 'SetAngle(x, y, z)' which needs all three angles to be set at once.

 

Unless someone can point out where I've missed something obvious, I think I'm pretty giving done trying to work this out - my maths just isn't up to it! I'll put my script and findings here for anyone else who wants to pick up the mantel in the future:

 

I came across this code to change local to global rotations. http://wikitest.nexusmods.com/index.php/Rotating_an_object_about_local_angles/axes

 

and wrote this code:

function setLocalAngles(ObjectReference ref, float localAngX, float localAngY, float localAngZ)
	if(localAngX==0 && localAngY==0) ; frequently the case - nothing clever needs to be done, save ourselves some maths
		ref.SetAngle(0, 0, localAngZ)
		return;
	endif

	;the world angles
	float worldAngX
	float worldAngY
	float worldAngZ

	localAngX=-localAngX
	localAngY=-localAngY
	localAngZ=-localAngZ

	;sine and cosine of local x, y, z angles
	float sx=sin(localAngX)
	float cx=cos(localAngX)
	float sy=sin(localAngY)
	float cy=cos(localAngY)
	float sz=sin(localAngZ)
	float cz=cos(localAngZ)

	;ZYX
	float r11=cz*cy
	float r12=-sz*cx+cz*sy*sx
	float r13=sz*sx+cz*sy*cx
	float r21=sz*cy
	float r22=cz*cx+sz*sy*sx
	float r23=cz*-sx+sz*sy*cx
	float r31=-sy
	float r32=cy*sx
	float r33=cy*cx

	;Extraction of worldangles from rotation matrix
	if (r13 >  0.9998)
		;positive gimbal lock
		worldAngX=-atan2(r32, r22)
		worldAngY=-90
		worldAngZ=0
	elseif (r13 < -0.9998)
		;negative gimbal lock
		worldAngX=-atan2(r32, r22)
		worldAngY=90
		worldAngZ=0
	else
		;no gimbal lock
		r23=-r23
		r12=-r12
		worldAngX=-atan2(r23, r33)
		worldAngY=-asin(r13)
		worldAngZ=-atan2(r12, r11)
	endif
	ref.SetAngle(worldAngX, worldAngY, worldAngZ)
Endfunction

float function atan2(float y, float x) ; returning degrees
	if x>0
		return atan(y/x)
	elseif x<0
		if y>=0
			return atan(y/x)+180.0
		elseif y<0
			return atan(y/x)+(-180.0)
		endif
	elseif x==0
		if y>0
			return 90
		elseif y<0
			return -90
		else ; undefined - we will return zero
			return 0.0
		endif
	endif
endfunction

setLocalAngles() uses the tan2 function given below, and sets the world angles for an object reference based on its local angles

 

Here I attempted to take the SetLocalAngle function given on the creation kit pages and reverse it to make a global to local converter - though I'm not convinced either are correct

float[] function getLocalAngles(ObjectReference ref)
	float[] localXYZAngles=new float[3]
	
	float globalXAngle=ref.GetAngleX()
	float globalYAngle=ref.GetAngleY()
	float globalZAngle=ref.GetAngleZ()
	
	float aConst=globalXAngle
	float dConst=globalYAngle
	float bConst=cos(globalZAngle)
	float cConst=sin(globalZAngle)
	
	float localAngleX=(aConst*bConst-cConst*dConst)/(bConst*bConst+cConst*cConst)
	float localAngleY=(aConst*cConst-dConst*bConst)/(bConst*bConst+cConst*cConst)
	float localAngleZ=globalZAngle
	
	localXYZAngles[0]=localAngleX
	localXYZAngles[1]=localAngleY
	localXYZAngles[2]=localAngleZ
	return localXYZAngles
endfunction

;take local angles convert to global and set  on ref
Function SetLocalAngle(ObjectReference ref, Float LocalX, Float LocalY, Float LocalZ)
	float AngleX = LocalX * Math.Cos(LocalZ) + LocalY * Math.Sin(LocalZ)
	float AngleY = LocalY * Math.Cos(LocalZ) - LocalX * Math.Sin(LocalZ)
	ref.SetAngle(AngleX, AngleY, LocalZ)
EndFunction

And here I thought I'd try some obviously bad maths to do rotations on the z-axis for global angles - this didn't work so well! Think my maths is waaaay off.

float[] function calculateXYAngles(float globalX, float globalY, float globalZ, float newGlobalZ)
	float[] newGlobalXY=new float[2]
	float angle=atan2(globalY, globalX)-globalZ
	
	float newAngle=angle+newGlobalZ
	
	newGlobalXY[0]=Math.sin(newAngle)*180
	newGlobalXY[1]=Math.cos(newAngle)*180
	return newGlobalXY
endfunction

Thanks for reading!

 

Cheers - dafydd99

Edited by dafydd99
Link to comment
Share on other sites

Ah that code is for oblivion and I think the order is different for Skyrim, which is ZYX, and this order doesn't change.

 

I'm testing this and what's annoying but interesting is that the changes to X and Y are not reflected in the Reference Window until after the Z rotating is done in the Render Window. Changes to the Z in the Reference Window doesn't change the X and Y angles even after hitting OK. It's a dynamic change exclusive to the Render Window(well makes sense as you're technically in the game), making it really hard to test and figure out the math required >_____________>.

 

Ok so I rotated a skull 90 on X angle(the skull is on its face with this angle)

Y and Z are 0.

 

I change the Z to -90.0 (top of skull is facing east)

 

X changed to 180.000 even

Y changed to 89.9802

 

so...

 

X -180.000

Y - 89.9802

Z - -90.0000

 

This is useless because the values aren't static. If I rotate it 90 the other way, X is 179.xxx. If I rotate it back to -90, X is 179.

 

So much for hoping to use regular math lol.

Edited by Rasikko
Link to comment
Share on other sites

 

Ah that code is for oblivion and I think the order is different for Skyrim, which is ZYX, and this order doesn't change.

Yes - quite right! But that code is very complete and covers all the possible six different orders, even the five not used. My code above uses order=6, the ZYX variant.

 

 

I'm testing this and what's annoying but interesting is that the changes to X and Y are not reflected in the Reference Window until after the Z rotating is done in the Render Window. Changes to the Z in the Reference Window doesn't change the X and Y angles even after hitting OK. It's a dynamic change exclusive to the Render Window(well makes sense as you're technically in the game), making it really hard to test and figure out the math required >_____________>.

Exactly!! There's no way to watch it as it changes. Also, beyond floating point inaccuracies it actually seems a bit buggy - if I've set snap-to-rotate to 90 degree intervals, sometimes the 'z' doesn't lock to this with say x=10, and instead does nearly 90 degrees, presumably with some part of it passed on to the x rotation.

 

Yes, the 179s are about floating point accuracies sneaking in I think - if you move and rotate stuff around enough that tends to happen without snap-to's on. I think we can ignore those - the .xxx's will be close enough (for my use in any case) as long as this rotation operation isn't done hundreds of times backwards and forwards. What I want it for is to take a starting rotation (in the X,Y and Z rotations) and just alter to match a new Z rotation (eg +35 degrees from the original) - the inaccuracies will only happen once and won't build up.

 

Thanks again for taking time to look into this, Rasikko.

Link to comment
Share on other sites

That's because they didn't use the proper name for it.

 

DefaultRotateHelperScript.psc

I'll just paste it here to save you the trouble:

 

 

 

ScriptName DefaultRotateHelper extends ObjectReference Default
{For use with "RotateHelperFree" Default script that toggles rotation from starting position to end position on activate}

import math
import game
import debug

Group Required_Properties
    int property DegreesToRotate = 45 auto const
    {default = 45}

    bool property CC = true auto
    {Rotate CounterClockwise? (default)}

    string property Speed = "medium" auto const
    {slow, medium(default), fast}

    Bool property alwaysOn = false auto const
    {always toggle back and forth? default = false}
EndGroup

Group Advanced_Properties CollapsedOnRef
    {Speed = time in seconds to rotate 45 degrees}
    float property speedSlow = 4.0 auto const
    float property speedMedium = 2.0 auto const
    float property speedFast = 1.0 auto const
    traptripwire property myTripwire auto
    {If not empty, look for an "Inactive" state on the ref in this property}
EndGroup

int currentPosition
int targetPosition
float mySpeed
int direction = 1

;************************************
Event OnLoad()
    if(speed == "slow")
        mySpeed = speedSlow
    elseif(speed =="medium")
        mySpeed = speedMedium
    elseif(speed == "fast")
        mySpeed = speedFast
    else
        mySpeed = speedSlow
    endif

    if CC
        direction = 1
    else
        direction = -1
    endif

    SetAnimationVariableFloat("fspeed", mySpeed * 2)
    registerForAnimationEvent(self,"done")

    if alwaysOn
        debug.trace("Rotate Helper: Auto-beginning")
        activate(self)
    endif
    
EndEvent

Event onActivate(objectReference triggerRef)
    currentPosition = 0
    targetPosition = DegreesToRotate
    doRotate()
endEvent

;************************************
event OnAnimationEvent(ObjectReference akSource, string asEventName)
    if asEventName == "done"
        if currentPosition == targetPosition && alwaysOn
            ; rotation move is over.  Am I looping?  reverse and reset if so.
            ;direction *= -1
            if targetPosition == 0
                targetPosition = DegreesToRotate
            else
                targetPosition = 0
            endif
        endif
        doRotate()
    endif
EndEvent

function doRotate()
    int DegreesRemaining = targetPosition - currentPosition
    debug.trace("Beginning DoRotate of "+DegreesRemaining*direction+" degrees for "+self)

    if DegreesRemaining > 90
        currentPosition += 90
        SetAnimationVariableFloat("fspeed", mySpeed * 2)
        SetAnimationVariableFloat("fvalue", currentPosition*direction)
        playAnimation("play01")
    elseif DegreesRemaining < -90
        SetAnimationVariableFloat("fspeed", mySpeed * 2)
        currentPosition -= 90
        SetAnimationVariableFloat("fvalue", currentPosition*direction)
        playAnimation("play01")
    elseif abs(DegreesRemaining) > 0
        SetAnimationVariableFloat("fspeed", (mySpeed * 2)*(abs(DegreesRemaining)/90.0))
        currentPosition = targetPosition
        SetAnimationVariableFloat("fvalue", currentPosition*direction)
        playAnimation("play01")
    else
        debug.trace("called DoRotate, but nothing to do.")
    endif    
endFunction

function doRotateOLD()
        ; figure out how much rotation we_re going to attempt in total.
    
    int i90degRotationsToPerform = Floor(DegreesToRotate/90)
    int iRotationLeftovers = DegreesToRotate - (i90degRotationsToPerform*90)
    debug.trace("Rotate Helper!")
    debug.trace("     DegreesToRotate: "+DegreesToRotate)
    debug.trace("     i90degRotationsToPerform: "+i90degRotationsToPerform)
    debug.trace("     iRotationLeftovers: "+iRotationLeftovers)

    ; register for animation event and start rotating
    int i = 0
    int iDegreesToRotate = 90
    if !CC
        iDegreesToRotate*-1  ; if flagged for counter-clockwise, then go backwards.
    endif
    while i < i90degRotationsToPerform
        SetAnimationVariableFloat("fvalue", 90)
        playAnimationAndWait("play01", "done")
        i += 1
    endwhile

    ; 90-degree increments are taken care of.  Play the rest now.
    if !CC
        -1*(iRotationLeftovers)
    endif
    SetAnimationVariableFloat("fvalue", iRotationLeftovers)
    playAnimationAndWait("play01", "done")

endFunction

;************************************

; Function Rotate(int Degrees, bool CC = false)
;     currentPosition = getAnimationVariableFloat("fvalue")

;     if(Degrees > 0)
;         if (!CC)
;             Degrees = -Degrees
;         endif

;         SetAnimationVariableFloat("fvalue", currentPosition + Degrees)
;         SetAnimationVariableFloat("fspeed", mySpeed * 2)
;         playAnimationAndWait("play01", "done")
;     endif
    
;     if(alwaysOn) && is3Dloaded()
;         if(CC)
;             CC = false
;         else
;             CC = true
;         endif
;         RotateLarge(DegreesToRotate, CC)
;     endif
; endFunction

; ;=======

; Function RotateLarge(int TotalDegrees, bool CC = false)
;     int countToRotate = Floor(TotalDegrees / 90)
;     float RotateRemainder = ((TotalDegrees as float / 90) - countToRotate as float) * 90

;     int i = 0
;     while (i < CountToRotate) && is3Dloaded()
;         Rotate(90, CC)
;         i += 1
;     endwhile

;     Rotate(RotateRemainder as int, CC)
; EndFunction

 

 

Edited by Rasikko
Link to comment
Share on other sites

Rasikko - that's awesome! Thanks again. So this script mentions "RotateHelperFree". I have neither this or the above script in my Skyirm resources (source of scripts being in scripts.zip in the data folder, which otherwise appears to include all the DLCxxx scripts). Is there another resource somewhere? I can see a "DLC2dunFahlbtharzRotateHelper180CW" and three other similar activators but that's it for rotatehelpers - and I think they won't be of use to me due to the fixed 180 degree movements.

 

I'm actually thinking this may be of limited use for me anyhow, as I probably can't avoid doing the calculations done in papyrus, and potentially called 'off cell', so not a local animation as such. But it certainly adds to the thread as a further option for anyone finding it in the future.

 

Cheers - dafydd99

Link to comment
Share on other sites

You also can't find that script because it's for Fallout 4. I see you're doing some deep searching and winding up in other forums lol. I think if this is a long term project you're doing, it wouldn't hurt to set aside some time to study geometry/trigonometry. Math has really thrived on the internet and you can find a ton of information for self teaching without having to rely on the math majors who assume everyone else knows what they know lol.

Edited by Rasikko
Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...