Bone angles etc

Need help? This is the right place!
Note, reporting bugs and issues should be in the Tech Support forum!

Moderator: PhilipJFry

Bone angles etc

Postby Dudekahedron » 03 Dec 2016, 23:42

Hi FAF,
I've been trying to figure out how to measure angles, specifically changes in angles, of bones. What I'd like to do is then set the angle of other bones to a manipulation of that value. The goal is to simulate a sort of double-hinged joint like the bones in a forearm.

What I came up with:
(State is initiated after its done being built)
Code: Select all
    HingeState = State{
        Main = function(self)
            WaitSeconds(2) #unpacking of the unit, you can ignore this

            while not self:IsDead() do
                WaitSeconds(0.5)     #necessary to stop endless update and game crash
                #Set up joints 1 and 2
                self.J1 = CreateRotator(self, 'joint1', 'x') #the two bones ill be manipulating later
                self.J2 = CreateRotator(self, 'joint2', 'x')

                self.Trash:Add(self.J1) #these things that are just required for whatever reason?
                self.Trash:Add(self.J2)

                #Get angle position
                local angle = {}
                angle.x, angle.y, angle.z = self:GetBoneDirection('pitch')
                #Im hoping this is a local x angle measured from starting orientation

                LOG('pitch angle is') #so i know what is happening
                LOG(angle.x)

                #Translate differences in angle
                #local R1 = {}
                local R2 = {}
                local R3 = {}
                local R4 = {}
                R1 = -0.0064*angle.x^2  #R1 to R4 come from the approximated hinge behavior for my specific set of bones
                R2 = 0.9653*angle.x
                R3 = 0.0089*angle.x^2
                R4 = -1.5412*angle.x
               
                LOG('J1 rotating by')
                LOG(R1+R2)

                LOG('J2 rotating by')
                LOG(R4+R3)

                self.J1:SetCurrentAngle(R1+R2) #moves joint1 and joint2 bones to their new positions
                self.J2:SetCurrentAngle(R3+R4)
            end
        end
    },

So that's what I have, but what ends up happening is J1 and J2 don't stop moving, they just keep bouncing to a new position.
Picture of set up for visualizing:
Image
Basically the code should rotate joint1 and joint2 as to keep the model representation "attached" to the small green cylinder at the top and the sphere at the bottom.
Any ideas?
Thanks
Check out my mod (in development): /viewtopic.php?f=41&t=13326
User avatar
Dudekahedron
Avatar-of-War
 
Posts: 118
Joined: 10 Apr 2016, 22:01
Has liked: 42 times
Been liked: 54 times
FAF User Name: Dudekahedron

Re: Bone angles etc

Postby Dudekahedron » 04 Dec 2016, 00:24

Better:
Code: Select all
    HingeState = State{
        Main = function(self)
            WaitSeconds(2) #unpacking of the unit, you can ignore this
            self.J1 = CreateRotator(self, 'joint1', 'x') #the two bones ill be manipulating later
            self.J2 = CreateRotator(self, 'joint2', 'x')
            self.Trash:Add(self.J1) #these things that are just required for whatever reason?
            self.Trash:Add(self.J2)
            local rotateold = {}

            while not self:IsDead() do

                WaitSeconds(0.1)
                #Set up joints 1 and 2

                #Get angle position
                local angle = {}
                angle.x, angle.y, angle.z = self:GetBoneDirection('pitch') #Im hoping this is a local x angle, and not a global one

                local blah = {}               
                blah.x, blah.y, blah.z = self:GetBoneDirection('DAB201')

                local rotate = {}
                rotate = MATH_IRound(Util.GetAngleInBetween(angle, blah))
                LOG('Difference is')
                LOG(rotate)

                if rotateold ~= rotate then

                    #Translate differences in angle
                    local R1 = {}
                    local R2 = {}
                    local R3 = {}
                    local R4 = {}

                    R1 = MATH_IRound(0.0064*rotate^2)  #R1 to R4 come from the approximated hinge behavior
                    R2 = MATH_IRound(-0.9653*rotate)
                    R3 = MATH_IRound(-0.0089*rotate^2)
                    R4 = MATH_IRound(1.5412*rotate)
               
                    LOG('J1 rotating by')
                    LOG(R1+R2)

                    LOG('J2 rotating by')
                    LOG(R4+R3)

                    self.J1:SetCurrentAngle(R1+R2)
                    self.J2:SetCurrentAngle(R3+R4)
                end
                rotateold = rotate
            end
        end
    },


Now joint1 and joint2 'blink' between correct locations, not ideal, but improving.
Another new issue; GetAngleBetweenBones() always produces a positive angle, so joint1 and joint2 will bend as if the gun is pointing upwards regardless of direction it actually points.

EDIT: I can solve the new issue by adding a bone that rotates with the entire turret that is perpendicular and points forward as opposed to up. Then I just measure the angle between the pitch bone and this new bone, then change the sign of the rotate variable appropriately. Testing this later.
Check out my mod (in development): /viewtopic.php?f=41&t=13326
User avatar
Dudekahedron
Avatar-of-War
 
Posts: 118
Joined: 10 Apr 2016, 22:01
Has liked: 42 times
Been liked: 54 times
FAF User Name: Dudekahedron

Re: Bone angles etc

Postby Dudekahedron » 04 Dec 2016, 20:19

Oh man was this a pain!
Sorted it out, here's the code for anyone looking at creating complicated motions:
Code: Select all
    HingeState = State{
        Main = function(self)
            WaitSeconds(2)
            #unpacking of the unit, you can ignore this, not even sure if its necessary tbh
           
            #Set up joints 1 and 2
            self.J1 = CreateRotator(self, 'joint1', 'x')
            #the two bones ill be manipulating later, see picture above
            self.J2 = CreateRotator(self, 'joint2', 'x')
            self.Trash:Add(self.J1)
            #these things that are just required for whatever reason?
            self.Trash:Add(self.J2)

            local rotateold = {}
            #I don't want to have the hinge constantly trying to move, so I create this variable to check against

            while not self:IsDead() do
            #the following gets the details on the pitch of the weapon, and if it has changed

                WaitSeconds(0.1)
                #this needs to exist and have a value that isn't 0. Changing this requires changing the starred line below

                local tune = {}
                tune.heading, tune.pitch = self:GetWeaponManipulatorByLabel('MainGun'):GetHeadingPitch()
                #This returns values in radians. Yeah. radians. What the hell. Took a while to figure that out.

                local rotate = {}
                rotate = MATH_IRound((180/3.14159)*tune.pitch) #Converting to degrees and rounding for nice numbers
                #Most of the supcom bones seem to twitch subtly, so without rounding rotate almost always changes

                if rotateold ~= rotate then #if the weapon pitch hasn't changed over the last 0.1 seconds, nothing happens

                    #Translate differences in rotate
                   
                    local R2 = {} #I used blender, and iterated through a few angles of the pitch bone to establish joint angles
                    local R3 = {} #Then I used excel to derive a polynomial. It was a 3rd order, but the first term was a tiny thing

                    local R5 = {}
                    local R6 = {}

                    R2 = (-0.0165*(rotate*rotate)) #Terms 2 and 3 from the joint1's poly
                    R3 = (1.1357*(rotate))

                    R5 = (0.0295*(rotate*rotate)) #Terms 2 and 3 from joint2's poly
                    R6 = (-1.9041*(rotate))
                    #Those paying attention may notice I dropped my C term; turns out I corrected for it with my model by
                    #accident. This is not something anyone will need to worry about, It was my own sloppiness that created its
                    #need, and the same sloppiness that accidentally fixed it.

                    rotate1 = MATH_IRound(R2+R3) #cleanliness
                    rotate2 = MATH_IRound(R5+R6)

                    self.J1:SetGoal(-rotate1):SetSpeed(50)
                    *GetHeadingPitch() produces radians, yet this takes degrees. But motion is reversed. What. The. Hell.
                    self.J2:SetGoal(-rotate2):SetSpeed(50)
                    #*******************Tune these speeds with the wait time to make a smooth motion
                end
                rotateold = rotate #set the new rotate value. This might be able to go in the if loop above, dunno.
            end
        end
    },


I annotated it heavily to help explain some of the weirdness. Major pain, but now that I've figured this general procedure out, future unit animations could be far more pleasing!

I also have a hunch that instead of using "while not self:IsDead() do" I could use something in the spirit of "if target exists then". Something I'll keep an eye out for in any case.
Check out my mod (in development): /viewtopic.php?f=41&t=13326
User avatar
Dudekahedron
Avatar-of-War
 
Posts: 118
Joined: 10 Apr 2016, 22:01
Has liked: 42 times
Been liked: 54 times
FAF User Name: Dudekahedron


Return to Help

Who is online

Users browsing this forum: No registered users and 1 guest