Author Topic: Advanced Ship Rotation Script  (Read 5672 times)

0 Members and 1 Guest are viewing this topic.

Advanced Ship Rotation Script
Okay, okay. Depends what you mean by advanced. However I was frustrated with FRED's inability to create events that roll a ship about its banking axis. Such a limitation is annoying to work around when writing missions that feature capital ships with beam cannons on either the top or bottom (Moloch, I'm looking at you.) Thus I've been teaching myself how to write scripts so that I could fashion what I needed.

I wrote a script that's activated the the SEXP "script-eval" and uses SEXP variables to specify at least the ship and target coordinates to point the banking vector towards. In addition, it allows you to optionally specify turning rates, a threshold variable for "nearness" of the two vectors,  acceleration and deceleration dampening parameters to smooth out the rotation, and some mission debugging options.

Code: [Select]
#Global Hooks

$GameInit:

[


spin = function()

if mn.SEXPVariables['rotationscriptfinished']:isValid() == true then --If you want to chain rotations, declare this variable in FRED
mn.SEXPVariables['rotationscriptfinished'].Value = 0         --When the algorithim is finished, it sets this variable equal to 1.
end      --Thus a chained SEXP event with the condition of this variable = 1
     --will chain rotations.
rotateship = true


--The following variables are VITAL to specify in FRED. If you don't specify these variables, the script will dump errors on you.
--I don't know what ship you want to rotate or to where, so it's your job to declare these.

if mn.SEXPVariables['shipnumber']:isValid() ~= true then --To specify the ship, create a variable "shipnumber" and
ba.error("Must specify 'shipnumber' SEXP variable") --use the object number of the ship given in the mission file.
end
shiptorotate = mn.SEXPVariables['shipnumber'].Value

if mn.SEXPVariables['targetX']:isValid() ~= true then --The SEXP variable "targetX" is the X coordinate you want
ba.error("Must specify 'targetX' SEXP variable") --the banking axis to point towards.
end
targetX=mn.SEXPVariables['targetX'].Value

if mn.SEXPVariables['targetY']:isValid() ~= true then --The SEXP variable "targetY" is the Y coordinate you want
ba.error("Must specify 'targetY' SEXP variable") --the banking axis to point towards.
end
targetY=mn.SEXPVariables['targetY'].Value

if mn.SEXPVariables['targetZ']:isValid() ~= true then --The SEXP variable "targetZ" is the Z coordinate you want
ba.error("Must specify 'targetZ' SEXP variable") --the banking axis to point towards.
end
targetZ=mn.SEXPVariables['targetZ'].Value


--These are more-advanced options that can be modified at will,
--but are not necessary to declare in order to run the script.

if mn.SEXPVariables['deceldamp']:isValid() == true then --Dampening on deceleration. The larger this value the
deceldamp = mn.SEXPVariables['deceldamp'].Value --longer the ship's "drift" will be after the rotation.
else
deceldamp=400
end

if mn.SEXPVariables['acceldamp']:isValid() == true then --Dampening on acceleration. The larger this value is
acceldamp = mn.SEXPVariables['acceldamp'].Value --the longer it will take to get to full rotational velocity.
else
acceldamp = 400
end

if mn.SEXPVariables['rotationspeed']:isValid() == true then --The rotation speed of your ship.
rotationspeed = (.1)*(mn.SEXPVariables['rotationspeed'].Value) --It's specified in units of .1. Thus,
else --A value in Fred of 10 will produce a rotation speed of
rotationspeed=10 --1.
end

if mn.SEXPVariables['threshold']:isValid() == true then --The absolute value of the (dot product of your desired banking and
threshold = (.0001)*(mn.SEXPVariables['threshold'].Value) --current banking vectors - 1). Below this threshold, the algorith shuts
else --off. In units of .0001. Thus a value of 0 corresponds to parallel vectors,
threshold=.005 --10000 corresponds to perpendicular vectors, and 20000 corresponds to
end --antiparallel vectors.

if mn.SEXPVariables['orientation']:isValid() == true then --THIS VALUE SHOULD BE EITHER 1 OR -1. It inverts the final pitch vector
orientation = mn.SEXPVariables['orientation'].Value --of the orientation matrix. Thus if the pitch with orientation 1 is
else --<1,0,-1>, it will be <-1, 0, 1> with orientation -1.
orientation = 1
end

--The following variables are for easily debugging your FRED mission.

if mn.SEXPVariables['showdotproduct']:isValid() == true then --Shows ingame the dot product of the desired and current pitch vectors.
showdotproduct = mn.SEXPVariables['showdotproduct'].Value --Is very useful in fixing your variables if your ship "hangs" or fails
else --to converge on a solution. Set the variable in FRED to 1 to enable this mode.
showdotproduct = 0
end

if mn.SEXPVariables['showdesiredbankvector']:isValid() == true then --Shows the desired banking vector ingame.
showdesiredbankvector = mn.SEXPVariables['showdesiredbankvector'].Value
else
showdesiredbankvector = 0
end

if mn.SEXPVariables['showcurrentbankvector']:isValid() == true then --Shows the current banking vector ingame.
showcurrentbankvector = mn.SEXPVariables['showcurrentbankvector'].Value
else
showcurrentbankvector = 0
end

if mn.SEXPVariables['showtargetcoords']:isValid() == true then --Shows the target coordinates ingame.
showtargetcoords = mn.SEXPVariables['showtargetcoords'].Value
else
showtargetcoords = 0
end

if mn.SEXPVariables['showtransbankingvector']:isValid() == true then --Shows the the banking vector, transformed to relative to
showtransbankingvector = mn.SEXPVariables['showtransbankingvector'].Value --the ship's current orientation.
else
showtransbankingvector = 0
end

rotatingship = mn.Ships[shiptorotate]
initial_rotational_velocity = rotatingship.Physics.RotationalVelocity
remaining_decel = deceldamp
remaining_accel = 1

end

]

#END



#Conditional Hooks

$State: GS_STATE_GAME_PLAY

$On Frame:

[


if rotateship == true then

--Convert target coordinates into the desired banking unit vector
rotatingship_position = rotatingship.Position
desired_point = ba.createVector(targetX,targetY,targetZ)
distance = rotatingship_position:getDistance(desired_point)
desired_bank_vector = (desired_point - rotatingship_position)/distance

--Separate the orientation matrix into its component heading bank and pitch vectors
current_orientation_matrix = rotatingship.Orientation
current_heading_vector = ba.createVector(current_orientation_matrix[1],current_orientation_matrix[2],current_orientation_matrix[3])
current_bank_vector = ba.createVector(current_orientation_matrix[4],current_orientation_matrix[5],current_orientation_matrix[6])
current_pitch_vector = ba.createVector(current_orientation_matrix[7],current_orientation_matrix[8],current_orientation_matrix[9])

--Transform the desired banking vector into a coordinate system based on the current orientation matrix
--such that in this coordinate system, the current orientation matrix equals:
--|1 0 0|Heading
--|0 1 0|Banking
--|0 0 1|Pitch
--This makes subsequent calculation easier.
transformed_desired_bank = ba.createVector(desired_bank_vector:getDotProduct(current_heading_vector),desired_bank_vector:getDotProduct(current_bank_vector),desired_bank_vector:getDotProduct(current_pitch_vector))

--Calculate a resulting pitch vector such that in the transformed coordinate system
--the pitch vector is perpendicular to the banking vector with a y-component of zero.
--The orientation variable is factored in here since there are two antiparallel pitch
--vectors that satisfy those parameters.
transformed_desired_pitch = ba.createVector(orientation*(-1)*(transformed_desired_bank[3]),0,orientation*desired_bank_vector[1])

--Use the cross product of the two prior vectors to create the desired heading vector.
transformed_desired_heading = (transformed_desired_bank:getCrossProduct(transformed_desired_pitch))

--Calculate the difference between the desired and current orientation vectors
delta_H_vector = (transformed_desired_heading - ba.createVector(1,0,0))
delta_B_vector = (transformed_desired_bank - ba.createVector(0,1,0))
delta_P_vector = (transformed_desired_pitch - ba.createVector(0,0,1))

--What I'm about to do does have rhyme and reason behind it, but is far more complicated and lengthy
--than what I feel like writing right now. Thus, you're just going to have to trust me on this or
--figure out why yourself. Here is where I calculate the heading, banking, and pitch rotational
--velocities that will move the orientation vector closer to the desired result.
H = (delta_P_vector[1])
B = (delta_H_vector[2])
P = (delta_B_vector[3])

--Normalize H, B, and P so that they become components of a unit vector.
H_norm = H/math.sqrt(H^2 + B^2 + P^2)
B_norm = B/math.sqrt(H^2 + B^2 + P^2)
P_norm = P/math.sqrt(H^2 + B^2 + P^2)

--Assemble the final turning vector for this iteration
turning_vector = ba.createVector(P_norm,H_norm,B_norm)

--If accleration dampening is in effect, this increases the remaining acceration factor
if remaining_accel < acceldamp then
remaining_accel = remaining_accel + 1
end

--Modifies the ship's rotational velocities.
rotatingship.Physics.RotationalVelocity = initial_rotational_velocity + (turning_vector*rotationspeed*ba.getFrametime() - initial_rotational_velocity)*(remaining_accel/acceldamp)

--Calculates the dot product of the current and desired banking vectors
dot_product = current_bank_vector:getDotProduct(desired_bank_vector)

--Displays debugging information if indicated
if showdotproduct == 1 then
gr.drawString(dot_product, 100, 100)
gr.drawString('dot product',10,100)
end

if showdesiredbankvector == 1 then
gr.drawString(desired_bank_vector[1],100,120)
gr.drawString(desired_bank_vector[2],250,120)
gr.drawString(desired_bank_vector[3],400,120)
gr.drawString('desired',10,120)
end

if showcurrentbankvector == 1 then
gr.drawString(current_bank_vector[1],100,150)
gr.drawString(current_bank_vector[2],250,150)
gr.drawString(current_bank_vector[3],400,150)
gr.drawString('current',10,150)
end

if showtargetcoords == 1 then
gr.drawString(targetX,100,180)
gr.drawString(targetY,250,180)
gr.drawString(targetZ,400,180)
gr.drawString('target',10,180)
end

if showtransbankingvector == 1 then
gr.drawString(transformed_desired_bank[1],100,210)
gr.drawString(transformed_desired_bank[2],250,210)
gr.drawString(transformed_desired_bank[3],400,210)
gr.drawString("TBank", 10,210)
end

--Determines if the desired and current banking vectors are close enough to parallel.
--If so, initiates the deceleration algorithm and sets the 'rotaionscripfinished' SEXP
--variable to be equal to 1.
if math.abs(dot_product - 1) < threshold then
rotateship = ending

if mn.SEXPVariables['rotationscriptfinished']:isValid() == true then
mn.SEXPVariables['rotationscriptfinished'].Value = 1
end

end


end

--Gradually decelerates the ship to zero rotational velocity.
if rotateship == ending and rotateship ~= null then

if remaining_decel == 0 then

rotateship = false
end

rotatingship.Physics.RotationalVelocity = turning_vector*turning_constant*(remaining_decel/deceldamp)
remaining_decel = remaining_decel - 1

end

]

#End


There are a few known issues with this code thus far. For one, it requires you to specify the number, not the name of the ship in the SEXP Variables. I for the life of me have not been able to get mn.SEXPVariables[].Value to properly read stings. Fortunately, a peek in the text of the mission file reveals the object number of the ship.

Second, specifying a target banking vector exactly antiparallel to the current banking vector yields undefined values because the algorithm divides by zero. It works just fine if they're close to antiparallel.

Third, it's possible for the algorithm to "hang" or fail to converge on some desired vectors, generally if they're too far away from the current banking vector. There's two solutions to this. If the algorithm hangs near your desired vector, you can crank up the threshold and  the deceleration dampening. It stops the algorithm early and lets the ship drift into a position close enough for government work.

The other solution is to chain two or more rotation commands together in such a way that one is "on the way" to another. In FRED, declaring a SEXP Variable called "rotationscriptfinished" instructs the script to change the value of the variable from 0 to 1 when the script is finished (though if you have deceleration dampening, that will still be calculated). Chained rotation commands can be made in FRED, triggered by this variable.

An example of chained rotation commands:

Code: [Select]
#Events ;! 12 total

$Formula: ( when
   ( has-time-elapsed 3 )
   ( modify-variable @shipnumber[1] 1 )
   ( modify-variable @targetX[0] 0 )
   ( modify-variable @targetY[0] 0 )
   ( modify-variable @targetZ[0] 0 )
   ( modify-variable @threshold[10] 1000 )
)
+Name: FirstRotationVariables
+Repeat Count: 1
+Interval: 1
+Team: 0

$Formula: ( when
   ( true )
   ( script-eval "spin()" )
)
+Name: TriggerFirstRotation
+Repeat Count: 1
+Interval: 1
+Chained: 0
+Team: 0

$Formula: ( when
   ( = 1 @rotationscriptfinished[0] )
   ( modify-variable @targetX[0] -1000 )
   ( modify-variable @targetY[0] 1000 )
   ( modify-variable @targetZ[0] 1000 )
   ( modify-variable @orientation[1] -1 )
   ( modify-variable @threshold[10] 50 )
)
+Name: SecondRotationVariables
+Repeat Count: 1
+Interval: 1
+Chained: 0
+Team: 0

$Formula: ( when
   ( true )
   ( script-eval "spin()" )
)
+Name: TriggerSecondRotation
+Repeat Count: 1
+Interval: 1
+Chained: 0
+Team: 0

Another issue is that time compression tends to throw this script off by a bit. If anyone in the community has any suggestions on how to fix that, it'd be greatly appreciated. I've set the rotational velocity to be proportional to ba.getFrametime() and I think the issue may lie in my system for dampening acceleration and deceleration. For the time being, locking the time compression is a viable solution.

I've attached two examples of how this script can be used in FRED. The simple one does a single rotation command on a Deimos Corvette. The complex one chains several complicated rotation commands together for some gyroscopic marvels.

[attachment deleted by admin]
If I could remember the names of all these particles, I'd be a botanist. --Enrico Fermi

 

Offline Aardwolf

  • 211
  • Posts: 16,384
Re: Advanced Ship Rotation Script
So, if I'm understanding this correctly...

It's a script to extend / interop with FRED, allowing you to make a ship rotate to make some local-coords vector point at some world-coords location?

Interesting idea... keep working on it, fix those bugs!

 

Offline Wanderer

  • Wiki Warrior
  • 211
  • Mostly harmless
Re: Advanced Ship Rotation Script
VERY NICE! Worthy of hot wing sauce.

Hmm... I guess i could work on making rotation targets for the sexps and the lua function i added some time ago. Incidentally did you happen to try those options?
Code: [Select]
SEXPS:
"ship-maneuver"
"ship-rot-maneuver"
"ship-lat-maneuver"
 ..

LUA:
doManeuver()
Do not meddle in the affairs of coders for they are soggy and hard to light

 
Re: Advanced Ship Rotation Script
No, I hadn't looked at those options. I can't find any documentation on the SEXPs or the LUA function that you mentioned. If you could elaborate on these, I'd greatly appreciate it.

As far as I was aware the only SEXP for adjusting the ship position was "set-object-facing." If these SEXPs perform as their names imply, this script is covered by prior art and will have been solely for my educational benefit.
If I could remember the names of all these particles, I'd be a botanist. --Enrico Fermi

 

Offline The E

  • He's Ebeneezer Goode
  • 213
  • Nothing personal, just tech support.
    • Steam
    • Twitter
Re: Advanced Ship Rotation Script
They are relatively recent additions to the code, and can be found in 3.6.11 builds.
« Last Edit: December 27, 2009, 07:41:02 am by The E »
If I'm just aching this can't go on
I came from chasing dreams to feel alone
There must be changes, miss to feel strong
I really need lifе to touch me
--Evergrey, Where August Mourns

  

Offline Wanderer

  • Wiki Warrior
  • 211
  • Mostly harmless
Re: Advanced Ship Rotation Script
Also an important difference is that the mentioned sexp and the lua functions all 'hack' into the control input of the ship. They may behave differently (ie. not as well as) than your example.
Do not meddle in the affairs of coders for they are soggy and hard to light

 

Offline Enioch

  • 210
  • Alternative History Word Writer
Re: Advanced Ship Rotation Script
HELL YEAH! I've wanted something like this for a long time, and I can tell you this will probably be used in the Renegade Legion Mod (if it ever sees the light of day). Capital ships rolling to present undamaged gun batteries to the enemy is pretty much Tactics-101 in that universe. :yes:

One limitation I think I've spotted: the fact that you define a single variable in FRED that specifies the rotating ship means that there cannot be more than one ship rotating at the same time, right? I've got no scripting experience, so I might be wrong. Don't hurt me if I am... :nervous:

Anyway, having ONE ship rotate is WAAAAAAYYY better than having none at all. Well done!
'Violence is the last refuge of the incompetent'  -Salvor Hardin, "Foundation"

So don't take a hammer to your computer. ;-)

 
Re: Advanced Ship Rotation Script
Ah...thanks E. I've been using the 3.6.10 build, so that explains a lot.

Also, thanks for the feedback Enioch.  I'm working on an improved version that covers some of the issues I brought up in my original post and I'll be sure to include the capacity to rotate multiple ships. That and some better naming conventions.
If I could remember the names of all these particles, I'd be a botanist. --Enrico Fermi

 

Offline Colonol Dekker

  • HLP is my mistress
  • 213
  • Aken Tigh Dekker- you've probably heard me
    • My old squad sub-domain
Re: Advanced Ship Rotation Script
New, for JAD4. COLOSSUSBASEBALLBAT !!
Campaigns I've added my distinctiveness to-
- Blue Planet: Battle Captains
-Battle of Neptune
-Between the Ashes 2
-Blue planet: Age of Aquarius
-FOTG?
-Inferno R1
-Ribos: The aftermath / -Retreat from Deneb
-Sol: A History
-TBP EACW teaser
-Earth Brakiri war
-TBP Fortune Hunters (I think?)
-TBP Relic
-Trancsend (Possibly?)
-Uncharted Territory
-Vassagos Dirge
-War Machine
(Others lost to the mists of time and no discernible audit trail)

Your friendly Orestes tactical controller.

Secret bomb God.
That one time I got permabanned and got to read who was being bitxhy about me :p....
GO GO DEKKER RANGERSSSS!!!!!!!!!!!!!!!!!
President of the Scooby Doo Model Appreciation Society
The only good Zod is a dead Zod
NEWGROUNDS COMEDY GOLD, UPDATED DAILY
http://badges.steamprofile.com/profile/default/steam/76561198011784807.png

 
Re: Advanced Ship Rotation Script
All right, I've made some changes to the code:

Code: [Select]
#Global Hooks

$GameInit:

[
--initialize tables

rotate_ship_bank = nil
rotate_ship_bank = {}

rotatingship_bank = nil
rotatingship_bank = {}

initial_rotational_velocity_bank = nil
initial_rotational_velocity_bank = {}

targetX_bank = nil
targetX_bank = {}

targetY_bank = nil
targetY_bank = {}

targetZ_bank = nil
targetZ_bank = {}

acceldamp_bank = nil
acceldamp_bank = {}

rotationspeed_bank = nil
rotationspeed_bank = {}

decelthreshold_bank = nil
decelthreshold_bank = {}

stopthreshold_bank = nil
stopthreshold_bank = {}

orientation_bank = nil
orientation_bank = {}

remaining_accel_bank = nil
remaining_accel_bank = {}


rotationcount_bank = 0

startbankrotation = function()

rotationcount_bank = rotationcount_bank + 1

if mn.SEXPVariables['rotationscriptfinished']:isValid() == true then --If you want to chain rotations, declare this variable in FRED
mn.SEXPVariables['rotationscriptfinished'].Value = 0         --When the algorithim is finished, it sets this variable equal to 1.
end      --Thus a chained SEXP event with the condition of this variable = 1
     --will chain rotations.


--The following variables are VITAL to specify in FRED. If you don't specify these variables, the script will dump errors on you.
--I don't know what ship you want to rotate or to where, so it's your job to declare these.

if mn.SEXPVariables['shipnumber']:isValid() ~= true then --To specify the ship, create a variable "shipnumber" and
ba.error("Must specify 'shipnumber' SEXP variable") --use the object number of the ship given in the mission file.
end
shiptorotate = mn.SEXPVariables['shipnumber'].Value

if mn.SEXPVariables['targetX']:isValid() ~= true then --The SEXP variable "targetX" is the X coordinate you want
ba.error("Must specify 'targetX' SEXP variable") --the banking axis to point towards.
end
targetX_bank[rotationcount_bank] = mn.SEXPVariables['targetX'].Value

if mn.SEXPVariables['targetY']:isValid() ~= true then --The SEXP variable "targetY" is the Y coordinate you want
ba.error("Must specify 'targetY' SEXP variable") --the banking axis to point towards.
end
targetY_bank[rotationcount_bank] = mn.SEXPVariables['targetY'].Value

if mn.SEXPVariables['targetZ']:isValid() ~= true then --The SEXP variable "targetZ" is the Z coordinate you want
ba.error("Must specify 'targetZ' SEXP variable") --the banking axis to point towards.
end
targetZ_bank[rotationcount_bank] = mn.SEXPVariables['targetZ'].Value


--These are more-advanced options that can be modified at will,
--but are not necessary to declare in order to run the script.

if mn.SEXPVariables['acceldamp']:isValid() == true then --Dampening on acceleration. The larger this value is
acceldamp_bank[rotationcount_bank] = mn.SEXPVariables['acceldamp'].Value --the longer it will take to get to full rotational velocity.
else
acceldamp_bank[rotationcount_bank] = 5
end

if mn.SEXPVariables['rotationspeed']:isValid() == true then --The rotation speed of your ship.
rotationspeed_bank[rotationcount_bank] = (.1)*(mn.SEXPVariables['rotationspeed'].Value) --It's specified in units of .1. Thus,
else --A value in Fred of 10 will produce a rotation speed of
rotationspeed_bank[rotationcount_bank] = .2 --1.
end

if mn.SEXPVariables['decelthreshold']:isValid() == true then --The absolute value of the (dot product of your desired banking and
decelthreshold_bank[rotationcount_bank] = (.0001)*(mn.SEXPVariables['decelthreshold'].Value) --current banking vectors - 1). Below this threshold, the algorithm begins
else --decelrating the ship. The SEXP is in units of .0001. Thus a value of 0
decelthreshold_bank[rotationcount_bank]=.1 --corresponds to parallel vectors 10000 corresponds to perpendicular vectors,
end --and 20000 to antiparallel vectors.

if mn.SEXPVariables['stopthreshold']:isValid() == true then --This is a second threshold value that instructs the ship to stop rotating
stopthreshold_bank[rotationcount_bank] = (.0001)*(mn.SEXPVariables['stopthreshold']) --if it is greater than the deceleration threshold, the script defaults it to a
else --tenth of the deceleration threshold. Also in units of .0001
stopthreshold_bank[rotationcount_bank] = decelthreshold_bank[rotationcount_bank]*.1
end

if stopthreshold_bank[rotationcount_bank] > decelthreshold_bank[rotationcount_bank] then
stopthreshold_bank[rotationcount_bank] = decelthreshold_bank[rotationcount_bank]*.1
end

if mn.SEXPVariables['orientation']:isValid() == true then --THIS VALUE SHOULD BE EITHER 1 OR -1. It inverts the final pitch vector
orientation_bank[rotationcount_bank] = mn.SEXPVariables['orientation'].Value --of the orientation matrix. Thus if the pitch with orientation 1 is
else --<1,0,-1>, it will be <-1, 0, 1> with orientation -1.
orientation_bank[rotationcount_bank] = 1
end

--The following variables are for easily debugging your FRED mission.

if mn.SEXPVariables['showdotproduct']:isValid() == true then --Shows ingame the dot product of the desired and current pitch vectors.
showdotproduct = mn.SEXPVariables['showdotproduct'].Value --Is very useful in fixing your variables if your ship "hangs" or fails
else --to converge on a solution. Set the variable in FRED to 1 to enable this mode.
showdotproduct = 0
end

if mn.SEXPVariables['showdesiredbankvector']:isValid() == true then --Shows the desired banking vector ingame.
showdesiredbankvector = mn.SEXPVariables['showdesiredbankvector'].Value
else
showdesiredbankvector = 0
end

if mn.SEXPVariables['showcurrentbankvector']:isValid() == true then --Shows the current banking vector ingame.
showcurrentbankvector = mn.SEXPVariables['showcurrentbankvector'].Value
else
showcurrentbankvector = 0
end

if mn.SEXPVariables['showtargetcoords']:isValid() == true then --Shows the target coordinates ingame.
showtargetcoords = mn.SEXPVariables['showtargetcoords'].Value
else
showtargetcoords = 0
end

if mn.SEXPVariables['showtransbankingvector']:isValid() == true then --Shows the the banking vector, transformed to relative to
showtransbankingvector = mn.SEXPVariables['showtransbankingvector'].Value --the ship's current orientation.
else
showtransbankingvector = 0
end

rotate_ship_bank[rotationcount_bank] = true
rotatingship_bank[rotationcount_bank] = mn.Ships[shiptorotate]
initial_rotational_velocity_bank[rotationcount_bank] = rotatingship_bank[rotationcount_bank].Physics.RotationalVelocity
remaining_accel_bank[rotationcount_bank] = 0



end

spinpitch = function(i)

frametime = ba.getFrametime()

--Convert target coordinates into the desired banking unit vector
rotatingship_position = rotatingship_bank[i].Position
desired_point = ba.createVector(targetX_bank[i],targetY_bank[i],targetZ_bank[i])
distance = rotatingship_position:getDistance(desired_point)
desired_bank_vector = (desired_point - rotatingship_position)/distance

--Separate the orientation matrix into its component heading bank and pitch vectors
current_orientation_matrix = rotatingship_bank[i].Orientation
current_heading_vector = ba.createVector(current_orientation_matrix[1],current_orientation_matrix[2],current_orientation_matrix[3])
current_bank_vector = ba.createVector(current_orientation_matrix[4],current_orientation_matrix[5],current_orientation_matrix[6])
current_pitch_vector = ba.createVector(current_orientation_matrix[7],current_orientation_matrix[8],current_orientation_matrix[9])

--Transform the desired banking vector into a coordinate system based on the current orientation matrix
--such that in this coordinate system, the current orientation matrix equals:
--|1 0 0|Heading
--|0 1 0|Banking
--|0 0 1|Pitch
--This makes subsequent calculation easier.
transformed_desired_bank= ba.createVector(desired_bank_vector:getDotProduct(current_heading_vector),desired_bank_vector:getDotProduct(current_bank_vector),desired_bank_vector:getDotProduct(current_pitch_vector))


--Calculate a resulting pitch vector such that in the transformed coordinate system
--the pitch vector is perpendicular to the banking vector with a y-component of zero.
--The orientation variable is factored in here since there are two antiparallel pitch
--vectors that satisfy those parameters.
transformed_desired_pitch = ba.createVector((orientation_bank[i]*(-1)*(transformed_desired_bank[3])),0,orientation_bank[i]*transformed_desired_bank[1])

--Use the cross product of the two prior vectors to create the desired heading vector.
transformed_desired_heading = (transformed_desired_bank:getCrossProduct(transformed_desired_pitch))


--Calculate the difference between the desired and current orientation vectors
delta_H_vector = (transformed_desired_heading - ba.createVector(1,0,0))
delta_B_vector = (transformed_desired_bank - ba.createVector(0,1,0))
delta_P_vector = (transformed_desired_pitch - ba.createVector(0,0,1))


--What I'm about to do does have rhyme and reason behind it, but is far more complicated and lengthy
--than what I feel like writing right now. Thus, you're just going to have to trust me on this or
--figure out why yourself. Here is where I calculate the heading, banking, and pitch rotational
--velocities that will move the orientation vector closer to the desired result.
H = (delta_P_vector[1])
B = (delta_H_vector[2])
P = (delta_B_vector[3])

--Normalize H, B, and P so that they become components of a unit vector.
H_norm = H/math.sqrt(H^2 + B^2 + P^2)
B_norm = B/math.sqrt(H^2 + B^2 + P^2)
P_norm = P/math.sqrt(H^2 + B^2 + P^2)

--Assemble the final turning vector for this iteration
turning_vector = ba.createVector(P_norm,H_norm,B_norm)

--If accleration dampening is in effect, this increases the remaining acceration factor
if remaining_accel_bank[i] < acceldamp_bank[i] then
remaining_accel_bank[i] = remaining_accel_bank[i] + frametime
end
if remaining_accel_bank[i] > acceldamp_bank[i] then
remaining_accel_bank[i] = acceldamp_bank[i]
end


--Calculates the dot product of the current and desired banking vectors
dot_product = current_bank_vector:getDotProduct(desired_bank_vector)

--Determines if the dot product is past the stopping threshold and stops the ship when reached.
if math.abs(dot_product - 1) <= stopthreshold_bank[i] then
rotatingship_bank[i].Physics.RationalVelocity = ba.createVector(0,0,0)
rotate_ship_bank[i] = false
end

--Determines "main path" rotation
if math.abs(dot_product - 1) > decelthreshold_bank[i] and rotate_ship_bank[i] ~= false then
rotatingship_bank[i].Physics.RotationalVelocity = initial_rotational_velocity_bank[i] + (turning_vector*rotationspeed_bank[i] - initial_rotational_velocity_bank[i])*(remaining_accel_bank[i]/acceldamp_bank[i])
end

--Determines if deceleration threshold is passed and begins decelerating the rotation. Also triggers rotationscriptfinished SEXP
if math.abs(dot_product - 1) <= decelthreshold_bank[i] and rotate_ship_bank[i] ~= false then
rotatingship_bank[i].Physics.RotationalVelocity = (turning_vector*rotationspeed_bank[i])*(math.abs(dot_product-1)/decelthreshold_bank[i])
if mn.SEXPVariables['rotationscriptfinished']:isValid() == true then
mn.SEXPVariables['rotationscriptfinished'].Value = 1
end
end




--Displays debugging information if indicated
if showdotproduct == 1 then
gr.drawString(dot_product, 100, 100)
gr.drawString('dot product',10,100)
end

if showdesiredbankvector == 1 then
gr.drawString(desired_bank_vector[1],100,120)
gr.drawString(desired_bank_vector[2],250,120)
gr.drawString(desired_bank_vector[3],400,120)
gr.drawString('desired',10,120)
end

if showcurrentbankvector == 1 then
gr.drawString(current_bank_vector[1],100,150)
gr.drawString(current_bank_vector[2],250,150)
gr.drawString(current_bank_vector[3],400,150)
gr.drawString('current',10,150)
end

if showtargetcoords == 1 then
gr.drawString(targetX_bank,100,180)
gr.drawString(targetY_bank,250,180)
gr.drawString(targetZ_bank,400,180)
gr.drawString('target',10,180)
end

if showtransbankingvector == 1 then
gr.drawString(transformed_desired_heading[1],100,210)
gr.drawString(transformed_desired_heading[2],250,210)
gr.drawString(transformed_desired_heading[3],400,210)
gr.drawString("THead", 10,210)

gr.drawString(transformed_desired_bank[1],100,225)
gr.drawString(transformed_desired_bank[2],250,225)
gr.drawString(transformed_desired_bank[3],400,225)
gr.drawString("TBank", 10,225)

gr.drawString(transformed_desired_pitch[1],100,240)
gr.drawString(transformed_desired_pitch[2],250,240)
gr.drawString(transformed_desired_pitch[3],400,240)
gr.drawString("TPitch", 10,240)
end



end


]

#END



#Conditional Hooks

$State: GS_STATE_GAME_PLAY



$On Frame:

[

--Runs the script for all currently rotating ships
for current_ship_bank = 1, rotationcount_bank do
if rotate_ship_bank[current_ship_bank] == true then
spinpitch(current_ship_bank)
end
end



]

$On Mission End:

[

--Resets tables
rotate_ship_bank = nil
rotate_ship_bank = {}

rotatingship_bank = nil
rotatingship_bank = {}

initial_rotational_velocity_bank = nil
initial_rotational_velocity_bank = {}

targetX_bank = nil
targetX_bank = {}

targetY_bank = nil
targetY_bank = {}

targetZ_bank = nil
targetZ_bank = {}

acceldamp_bank = nil
acceldamp_bank = {}

rotationspeed_bank = nil
rotationspeed_bank = {}

decelthreshold_bank = nil
decelthreshold_bank = {}

stopthreshold_bank = nil
stopthreshold_bank = {}

orientation_bank = nil
orientation_bank = {}

remaining_accel_bank = nil
remaining_accel_bank = {}


rotationcount_bank = 0


]

#End

Firstly and most importantly I fixed the hanging issue that was plaguing the script. I thought it was an issue with the algorithm itself, but it turns out I mis-specified a variable in one of the calculations.

Secondly, this script also seems to work just fine for time compression. If you specify the threshold too low and crank the compression to x64, maybe it will miss its target, but I haven't managed to break it yet.

Thirdly, I managed to overhaul the script so that it is capable of rotating multiple ships. A few notes on this feature:

 1) The SEXP variable triggered when a rotation is completed will trigger when ANY rotation is completed, so chaining may be difficult. If any FREDder was planning a Sathanas ballet, I apologize. (Now that would be great for JAD4)

2) The SEXP variables read from the mission file are the same for every ship. When you call the script in FRED, it will read the current values of the rotation variables and then store them. Thus you can specify variables, trigger the script, specify new variables and trigger the script for another ship. An example of how this is done is included in the attached demonstration mission.

3) This script doesn't work when called simultaneously by two or more separate FRED events. It has to execute before you can change variables and call it again. Thus in my example mission, a 1-second delay between the two ships. Again, a slight problem for the picture-perfect Sathanas Ballet.

4) The debugging options only work for one ship rotating still. Hopefully though with this rewriting, they won't be needed much.


I've made a few smaller changes. I've redone a lot of variable names so that they are incredibly unlikely to interfere with the performance of other scripts. I'm considering making modified versions of this script for the heading and pitch vectors, so that makes my life easier. What's important for you is that the function to call this script has changed to "startbankrotation()."

Finally, I've eliminated the deceleration dampening system. Instead, the script uses the dot product value to calculate deceleration. So instead, you specify a threshold dot product, "decelthreshold" beyond which the script begins decelerating the ship. Then you specify "stopthreshold", which is the value at which the script stops the ship completely and shuts off. The rotationfinished SEXP is triggered at the deceleration threshold. More details can be found in the comments in the code.

Any further feedback is appreciated, and if any of you manage to break the script, let me know how you did so and I'll see about fixing it.  Attached is a demonstration mission.

[attachment deleted by admin]
If I could remember the names of all these particles, I'd be a botanist. --Enrico Fermi

 

Offline Colonol Dekker

  • HLP is my mistress
  • 213
  • Aken Tigh Dekker- you've probably heard me
    • My old squad sub-domain
Re: Advanced Ship Rotation Script
I've downloaded this, and appreciate the work that went into this first bold step on the epic  journey towards 3d Deimos/Meson bomb pong.
 
 
Srsly though, i'm i'll look through this when I can.
(I'm out of the house right now)
Campaigns I've added my distinctiveness to-
- Blue Planet: Battle Captains
-Battle of Neptune
-Between the Ashes 2
-Blue planet: Age of Aquarius
-FOTG?
-Inferno R1
-Ribos: The aftermath / -Retreat from Deneb
-Sol: A History
-TBP EACW teaser
-Earth Brakiri war
-TBP Fortune Hunters (I think?)
-TBP Relic
-Trancsend (Possibly?)
-Uncharted Territory
-Vassagos Dirge
-War Machine
(Others lost to the mists of time and no discernible audit trail)

Your friendly Orestes tactical controller.

Secret bomb God.
That one time I got permabanned and got to read who was being bitxhy about me :p....
GO GO DEKKER RANGERSSSS!!!!!!!!!!!!!!!!!
President of the Scooby Doo Model Appreciation Society
The only good Zod is a dead Zod
NEWGROUNDS COMEDY GOLD, UPDATED DAILY
http://badges.steamprofile.com/profile/default/steam/76561198011784807.png

 

Offline Rodo

  • Custom tittle
  • 212
  • stargazer
    • Steam
Re: Advanced Ship Rotation Script
I find this very interesting, I think I'll be testing this when I get some free time.
el hombre vicio...