Hard Light Productions Forums
Modding, Mission Design, and Coding => The Modding Workshop => Topic started by: Scooby_Doo on November 29, 2012, 11:13:17 pm
-
I must be missing some details/information...
The physical subsystem must also have a "$triggered:" but what's the value for it?
The table entry looks like this:
$Subsystem: PortMainGearDoorInner, 10, 1
$flags: ("untargetable")
$animation: triggered
$type: scripted
+sub_type: 0
+delay: 0
+relative_angle:0,0,90
+velocity: 0,0,10
+acceleration: 0,0,5
+time: 2000
and the sexp looks like this:
every-time
key-pressed q
free-rotating-subsystem Alpha 1 FrontLandingGear
key-reset q
but nothing happens, any ideas?
-
the submodel prop has no value. its just $triggered: (with the : ) at least thats what i get from my own nukemod models. i think the problem may be with the scripted type (which i think works from lua not through sexps, though im not totally sure).
you can try changing the type to something else, like afterburner and see if the animation plays, this will eliminate model/table configuration as an issue. animation code is a glitchy system when it does work.
e:
after looking at the scripting.html, it appears that the function to play an animation of type scripted is marked as being expiremental. so im writing a short script you should be able to test it with. two functions that can be called with a scrip eval to forward and reverse play an animation
#Conditional Hooks
$Application: FS2_Open
$On Game Init:
[
--do forward animations
function fanim(sname)
--not a string, bail
if type(sname) ~= "string" then
return false
end
--get a ship handle
local shp = mn.Ships[sname]
if shp:isValid() then
--play animation forward
shp:triggerAnimation("scripted",0,true)
return true
end
return false
end
--do backwords animation
function ranim(sname)
--not a string, bail
if type(sname) ~= "string" then
return false
end
--get a ship handle
local shp = mn.Ships[sname]
if shp:isValid() then
--play animation in reverse
shp:triggerAnimation("scripted",0,false)
return true
end
return false
end
]
#End
stick that in a somename-sct.tbm and then use a script-eval sexp to call functions fanim(shipname) and ranim(shipname) (the first plays an animation forward and the second plays it in reverse). shipname is a string, such as "Alpha 1". the full string for the sexp should be "[fanim("Alpha 2")]". this will play an animation of type scripted at subtype 0. thats assuming it works at all and doesnt crash.
-
There's also a new sexp in nightlies I think, called trigger-subobject-animation. You specify which type (in this case scripted) and optionally the specific subobject and just call it. You can also specify if you want the animation to be reversed, or for the animation to snap to its end position (in effect ignoring the speeds and instantly rotating).
-
Alright unless I'm doing something completely wrong, there's something wrong with the rotation. I've been doing some checking to see if i can figure out what I'm doing. I've done the simple cube ship with a rotation wheel. Now when I setup the wheel to rotate on the z-axis, so it's like a helicopter blades, it works perfectly. Then I rotated the wheel so it wouldn't spin vertically flat (tilted it), things broke. Instead of continuing to rotate like helicopter blade, it wobbled back and forth, i.e. it was rotating on the world's Z axis and not the subobject's Z axis.
Hmmm i wonder if uvec and fvec are needed... checking
-
YES!!! That was the solution...
if your subsystem is not on the classic top/bottom flat, you must use the uvec and fvec. This includes everything, not just turrets! Someone should put a sticky note on the wiki about this.
-
There's also a new sexp in nightlies I think, called trigger-subobject-animation. You specify which type (in this case scripted) and optionally the specific subobject and just call it. You can also specify if you want the animation to be reversed, or for the animation to snap to its end position (in effect ignoring the speeds and instantly rotating).
Well until someone ports the tables from saga over i'm stuck with it. The newer builds throw errors and quits. I've looked at the tables and went ugh at the thought.
edit: Sorry nuke, not having luck with the scripts. I don't think it's actually starting, added a couple of gr.DrawString ("") messages and notta. Is this how the event is suppose to look like?
$Formula: ( every-time
( key-pressed "Q" )
( script-eval "[fanim('Alpha 1')]" )
( send-message
"#Command"
"High"
"testing"
)
( key-reset "Q" )
)
double edit: ok found a extra quote but now its throwing this error Fixed that one (quote in quotes)
triple edit: ok it crashes when it tries to use the graphics.drawscreen, no biggie. I do know that the script is being called. I removed them and nothing rotates. Back to doing more checking. :blah:
-
Ya! Finally got it working. Turns out I was rotating in the wrong direction, so the landing gears never appeared outside the hull.
Two lua questions, is it possible to see the angle that the subobject current is and is it possible to see subobject properties? I'm thinking of something like this:
$RotationDelta:0,90,0
$RotationDirection:0,1,0
$RotationLimit would stop the rotation when it reaches limit and $RotationDirection tells it clockwise/counterclockwise. That way all you would have to do is call a function like DoAnimation("Alpha 1", "FrontLandingGear", true) to tell it to bring down the landing gear and DoAnimation ("Alpha 1", "FrontLandingGear" false) to raise it back up.
-
in script you just get the ship handle (ship = mn.Ships["shipname"]), and then the subsystem handle (subsys = ship["subsystemname"]). from that you can get the submodel orientation (subsys.Orientation). if you want to rotate it for example 90 degrees on the x axis, just multiply it orientation by a matrix that represents your change in rotation (subsys.Orientation = subsys.Orientation * ba.createOrientation(math.rad(90),0,0)) ). its slightly more complicated than that though, for example euler angles sometimes produce singularities and multiplication order matters.
way i would do it is id set up a begin matrix and an end matrix, and then interpolate between them (scripting has a function for that). you could map any data within a fixed range to the interpolation value and get an intermediate matrix. take this a step further you can make a keyframe based animation system, where each key in the time graph is a matrix, you can find the previous and next key for a given time graph, and get an interpolated matrix from that. mapping also need not be made linear. frankly even with the animation code, the submodel animation capabilities of the engine are archaic at best. im considering (eventually) writing such a script, because i need it for an animation heavy model im (sort of) working on.
im also not sure if scripted animations break collision detection or not, thats something that needs to be tested.
-
Hmmm well my lua knowledge is VERY limited.
I'm getting this error:
LUA ERROR: [string "animation-sct.tbm - On Game Init"]:18: attempt to index global 'base' (a nil value)
function DoAnimation(shipName, subsystemName)
--not a string, bail
if type(shipName) ~= "string" then
return false
end
if type (subsystemName) ~= "string" then
return false
end
--get a ship handle
local shp = mn.Ships[shipName]
if shp:isValid() then
subsystem = shp["FrontLandingGear"]
if subsystem:isValid () then
subsystem.Orientation = subsystem.Orientation * base.CreateOrientation (math.rad ((90),0,0))
--play animation forward
shp:triggerAnimation("scripted",0,true)
return true
end
end
return false
end
How do you import libaries ("base")?
-
base.CreateOrientation (math.rad ((90),0,0))
I don't know if Lua is case-sensitive or not, but the function's name is createOrientation. Also, you got the argument parentheses mixed up.
Presumably this is what you want:
base.createOrientation(math.rad(90), 0, 0)
-
lua is case sensitive. im mostly quoting for memory so do look stuff up in the scripting.html to make sure. i alwayse got at the base library with ba.functionName(). most of the functions are lower case for the first word and upper case for all other words, there are some exceptions, things like mn.Ships (technically thats an indexer not a function) for example. all built in lua functions seem to be lower case. also any functions that return handles should be checked with handle:isValid() before use, which returns true if its good.
-
Also it doesn't like base for some reason... ba is good though. Making a lot of progress though!
Ok next question, is it possible to read properties from a subsystem? For example $RotateX=0 $RotateY=90 $RotateZ=0 that way I can do something like this:
xAxis = readProperty (ship)
yAxis = readProperty (ship)
zAxis = readProperty (ship)
subsystem.Orientation = subsystem.Orientation * ba.createOrientation (math.rad (xAxis), math.rad (yAxis), math.rad (zAxis)) -- Whatever the actual formula is needed
I couldn't find anything about properties in the scripting.html file.
-
i dont think those are in. problem is you need to add a function to lua.cpp for every little piece of data you need access to. this makes lua.cpp a very big pile of code. its usually trivial for someone to go in and add one, but code freeze. i usually just put the data in another file and parse it with lua, and that works fine for any unchanging piece of data.
-
i dont think those are in. problem is you need to add a function to lua.cpp for every little piece of data you need access to.
Actually it should have looked more like this:
xAxis = readProperty (ship, subsystemName, "$RotateX")
yAxis = readProperty (ship, subsystemName, "$RotateY")
zAxis = readProperty (ship, subsystemName, "$RotateZ")
That way you don't need a tons of extra functions to read data, just one function that takes the ship/object, the subsystem to use and the property in quesetion.
Alternatively, I can always do this:
DropBearcatLandingGear (shipName)
DoAnimation (shipName, "FrontLandingGear", 0, 90, 9) -- Front Gear
DoAnimation (shipName, "PortLandingGear", 90,0,0) -- Port landing gear
DoAnimation (shipName, "StarLandingGear", -90, 0, 0) -- starboard landing gear
end
RaiseBearcatLandingGear (shipName)
DoAnimation (shipName, "FrontLandingGear", 0,- 90, 0) -- Front Gear
DoAnimation (shipName, "PortLandingGear", -90,0,0) -- Port landing gear
DoAnimation (shipName, "StarLandingGear", 90, 0, 0) -- starboard landing gear
end
Have one for each ship type that needs animation.
-
Ya! This is working great... actually I kinda like having the rotation info in the script file over the pof file, far easier to fix and update.
Ok now how would I go about making it actual rotate to the destination angle rather than just snap?
-
use getInterpolated(). you need to call it like this
orientA:getInterpolated(orientB, v)
where orientA is your base orientation, and orientB is your destination orient. v is a value between 0 and 1. if the value is 0 you get orientA, and if its 1 you get orientB, or any intermediate orientation if its in the middle somewhere. in your case you need to figure out how long the animation will run, say 5 seconds. set a timestamp, and then while its good compute the current time and map it to the range of 0 to 1.
init code, call once when you want to start an animation
--create start and end orientations
orientA = ba.createOrientation(0,0,0)
orientB = ba.createOrientation(math.rad(90),0,0)
--how long the animation will run
animTime = 5
--setup timing info
startTime = mn.getMissionTime()
timeStamp = starttime + animTime
then call this every frame, this will animate the submodel up till the point that the timestamp expires, and wont work anymore (you can always re-run the init if you want to run another animation).
--animate while we still have time left
if mn.getMissionTime() < timestamp then
local animT = (mn.getMissionTime() - startTime) / animTime
local orientNew = orientA:getInterpolated(orientB, animT)
--apply to subystem now (note that subsystem needs to be a valid subsystem handle)
subsystem.Orientation = orientNew
end
there are probibly neater ways to do this though. but thats the basicly how timing is done. if you want to start the animation some times later, use another timestamp, wait for it to expire, then init the animation and run the on frame code. if you want to animate backwars, swap orientA and orientB. you also arent limited to time based animation, you can say link things like control surfaces to angular velocity or control input and the like and make those animate based on user input. you are also not limited to simple 90 degree on one axis rotations. you can rotate on multiple axes with this method as well.
-
Perfect, but there's one question, multiple instances. For example someone starts the animation sequence for a ship then starts it for another one. What do you think would be the best solution for that? I was thinking of an array of structs { sourceAngle, destAngle, ship } I assume the $On Game Init function (which the sexp calls) handles adding entries to the list while the $On Frame handles the actual processing the animation and removal of finished animations.
-
in that case make it both of these code snippets functions. i didnt do that because i just wanted to show how timing works. instead of using globals as i had done, stick all the parameters into a table, and return it from the function. this can be passed to the frame function. you can also include in the table the subsystem handle so you dont have to look it up later.
--takes subystem handle, orientation a, oritentation b, and animation length, returns a table or nil on failure
function initAnimation(subSys, oriA, oriB, animT)
--make sure handle is valid
if not subSys:isValid() then
return nil
end
--create a table
local t = {}
--subsystem handle
t.subsys = subSys
--create start and end orientations
t.orientA = oriA
t.orientB = oriB
--how long the animation will run
t.animTime = animT
--setup timing info
t.startTime = mn.getMissionTime()
t.timeStamp = t.startTime + t.animTime
return t --now contains everything we need to know for our animation
end
--now we just call it with the table, returns true when an animation frame is done, false when the the animation fails or is over
function doAnimation(t)
--first make sure the subsystem is still around (it could have been destroyed since the animation started
if not t.subsys:isValid() then
return false
end
--animate while we still have time left
if mn.getMissionTime() < t.timeStamp then
local animT = (mn.getMissionTime() - t.startTime) / t.animTime
local orientNew = t.orientA:getInterpolated(t.orientB, animT)
--apply to subystem now (note that subsystem needs to be a valid subsystem handle)
t.subsys.Orientation = orientNew
else --timestamp expired
return false
end
return true
end
you just stick that code in $OnGameInit: and you can call it from any other hook (in theory). now there is another problem, and that is keeping track of all the animation tables when you have a dozen animating subsystems running all at the same time. this is lua so we can just stick em all into another table then just iterate over it every frame so that all animations can be processed.
--all animations go here
animationList = {}
--same arguments as initAnimation, it just uses a global table instead
function addAnimation(sub, oa, ob, at)
--create an animation
local t = initAnimation(sub, oa, ob, at)
--t may be nil
if t then
--add it to the animation list
table.insert(animationList, t)
end
end
--no arguments, no return, just call once every frame
function runAnimations()
for i=1, #animationList do
local b = doAnimation(animationList[i])
--remove items from the list when they fail or are completed
if not b then
table.remove(animationList, i)
end
end
end
that can go in $onGameInit as well. then you can call addAnimation() at any time during a mission to create an animation for a subsystem. then call runAnimations() once every frame. these functions automatically maintain the animationList table. yo may need to re-init the animationList table in $OnMissionStart: (animationList = {}).
-
Success! It's not quite what you coded and it may not be the prettiest, but it does the job. Give it the destination angle and the time to do it and it'll do the animation :yes:
I need to fix a few things before I'll release it. Also I'm looking for the best way to include a "#include<>" so people wouldn't have to modify the table that much.
Also I just had a brilliant idea, capture the ships change in x/y/z axis and I can develop a thrust vectoring subsystems!
-
Do you know where the default locations that lua looks for files [using the require ()]
I'm getting this list
LUA ERROR: [string "animation-sct.tbm - On Game Init"]:77: module '.\scripts\Bearcat.lua' not found:
no field package.preload['.\scripts\Bearcat.lua']
no file '.\\\scripts\Bearcat\lua.lua'
no file 'D:\Wing Commander Saga\lua\\\scripts\Bearcat\lua.lua'
no file 'D:\Wing Commander Saga\lua\\\scripts\Bearcat\lua\init.lua'
no file 'D:\Wing Commander Saga\\\scripts\Bearcat\lua.lua'
no file 'D:\Wing Commander Saga\\\scripts\Bearcat\lua\init.lua'
no file '.\\\scripts\Bearcat\lua.dll'
no file 'D:\Wing Commander Saga\\\scripts\Bearcat\lua.dll'
no file 'D:\Wing Commander Saga\loadall.dll'
no file '.\.dll'
no file 'D:\Wing Commander Saga\.dll'
no file 'D:\Wing Commander Saga\loadall.dll'
Also I assume you can't have code spread out on two seperate table files, global variables and stuff won't be seen I'm guessing.
Edit: shot... spoke to soon.. major problem.
Rotations you give are done to the world axis, not the subobjects local axis. There should be a way of converting one to another (at least one i can wrap my brain around)
-
using functions like require and dofile looks for things in fs root. this is generally not the way freespace mods are supposed to work. instead you should use cfile to load your scripts from file, and then run the code with loadstring. theres a thread with a code example here (http://www.hard-light.net/forums/index.php?topic=74547.msg1473531#msg1473531).
ideally these built in functions should be modified to use the game's filesystem, and include some anti cheat and module security features (crced lua files and mod-approved binary modules), which will come in handy if multiplayer ever utilizes scripting. this probibly should be done before loadstring is depricated in future lua versions.
as for world/local transform conversion you just need to multiply your local matrix by its parent's orientation. i want to say its shipOri*subSysOri but im not sure. matrix multiplication is non commutative, so a*b != b*a. so if that dont work try it the other way around. if your transforming vectors you can just use orientation:unrotateVector(vector) or orientation:rotateVector(vector).
-
Bookmarked it to read latter tonight. I want to have only the necessary code for each ship to come with the model, and the main lua in a seperately held tbm file. Course there needs to be some editing on whoever uses it, but only trivial adding a few lines.
The way I'm doing it right now every .01 of a second i adjust the rotation the some amount (total rotation / total amount of time [seconds])
#Conditional Hooks
$Application: FS2_Open
$On Game Init:
[
lastFrameTime = mn.getMissionTime ()
animationQueue = {}
function CreateOrientation (x, y, z)
return ba.createOrientation (math.rad(x),math.rad(y),math.rad(z))
end
function SetupAnimation(shipName, subsystemName, destOrientation, rotationTime)
if type(shipName) ~= "string" then
return false
end
if type (subsystemName) ~= "string" then
return false
end
local shp = mn.Ships[shipName]
-- Make sure we actually have a ship
if shp == nil then
return false
end
if shp:isValid() then
subsystem = shp[subsystemName]
-- Make sure we actually have a subsystem
if subsystem == nil then
return false
end
if subsystem:isValid () then
-- Simply add it to the animation queue
AddAnimationToQueue (shp, subsystem, destOrientation, rotationTime)
return true
end
end
return false
end
function AddAnimationToQueue (ship, subsystem, destOrientation, timeToLive)
totalNumberOfSteps = timeToLive * 100
animation = {}
animation.ship = ship
animation.subsystem = subsystem
animation.destOrientation = destOrientation
animation.runTime = timeToLive
-- Find the delta difference between the source and destination angles
animation.deltaX = destOrientation.p - subsystem.Orientation.p
animation.deltaY = destOrientation.b - subsystem.Orientation.b
animation.deltaZ = destOrientation.h - subsystem.Orientation.h
-- Number of degrees to move during each animation step
animation.deltaX = (animation.deltaX / totalNumberOfSteps)
animation.deltaY = (animation.deltaY / totalNumberOfSteps)
animation.deltaZ = (animation.deltaZ / totalNumberOfSteps)
table.insert (animationQueue, animation)
end
function ProcessAnimation (animation)
pitch = animation.subsystem.Orientation.p
bank = animation.subsystem.Orientation.b
heading = animation.subsystem.Orientation.h
newX = pitch + animation.deltaX
newY = bank + animation.deltaY
newZ = heading + animation.deltaZ
animation.subsystem.Orientation = ba.createOrientation (newX, newY, newZ)
end
--require (".\\scripts\\Bearcat.lua")
function LowerBearcatLandingGear (shipName)
SetupAnimation (shipName, "FrontLandingGear", CreateOrientation (-90, 0, 0), 1.5 )
SetupAnimation (shipName, "PortMainLandingGear", CreateOrientation (0, -90, 0), 1.5)
SetupAnimation (shipName, "StarMainLandingGear", CreateOrientation (0, 90, 0), 1.5)
SetupAnimation (shipName, "PortDoor01", CreateOrientation (0, -90, 0), .5)
SetupAnimation (shipName, "PortDoor02", CreateOrientation (0, -90, 0), .5)
SetupAnimation (shipName, "PortDoor03", CreateOrientation (0, -90, 0), .5)
SetupAnimation (shipName, "StarDoor01", CreateOrientation (0, 90, 0), .5)
SetupAnimation (shipName, "StarDoor02", CreateOrientation (0, 90, 0), .5)
SetupAnimation (shipName, "StarDoor03", CreateOrientation (0, 90, 0), .5)
SetupAnimation (shipName, "MissileBayPortDoor", CreateOrientation (0, -90, 0), 2.5)
SetupAnimation (shipName, "MissileBayPortDoor01", CreateOrientation (0, -90, 0), 2.5)
SetupAnimation (shipName, "MissileBayStarDoor", CreateOrientation (0, 90, 0), 2.5)
SetupAnimation (shipName, "MissileBayStarDoor01", CreateOrientation (0, 90, 0), 2.5)
end
function RaiseBearcatLandingGear (shipName)
SetupAnimation (shipName, "FrontLandingGear", CreateOrientation (0, 0, 0), 1.5 )
SetupAnimation (shipName, "PortMainLandingGear", CreateOrientation (0, 0, 0), 1.5)
SetupAnimation (shipName, "StarMainLandingGear", CreateOrientation (0, 0, 0), 1.5)
SetupAnimation (shipName, "PortDoor01", CreateOrientation (0, 0, 0), 3.5)
SetupAnimation (shipName, "PortDoor02", CreateOrientation (0, 0, 0), 3.5)
SetupAnimation (shipName, "PortDoor03", CreateOrientation (0, 0, 0), 3.5)
SetupAnimation (shipName, "StarDoor01", CreateOrientation (0, 0, 0), 3.5)
SetupAnimation (shipName, "StarDoor02", CreateOrientation (0, 0, 0), 3.5)
SetupAnimation (shipName, "StarDoor03", CreateOrientation (0, 0, 0), 3.5)
SetupAnimation (shipName, "MissileBayPortDoor", CreateOrientation (0, 0, 0), 2.5)
SetupAnimation (shipName, "MissileBayPortDoor01", CreateOrientation (0, 0, 0), 2.5)
SetupAnimation (shipName, "MissileBayStarDoor", CreateOrientation (0, 0, 0), 2.5)
SetupAnimation (shipName, "MissileBayStarDoor01", CreateOrientation (0, 0, 0), 2.5)
end
function OpenLandGear(ship)
--if (ship.Class.Name == "F-104D Bearcat") then
LowerBearcatLandingGear (ship)
--end
end
function CloseLandGear(ship)
--if (ship.Class.Name == "F-104D Bearcat") then
RaiseBearcatLandingGear (ship)
--end
end
]
$State: GS_STATE_GAME_PLAY
$On Frame:
[
-- Make sure animationQueue exists and isn't empty (no sense in processing nothing)
if (animationQueue == nil) then
return false
end
if (#animationQueue == 0) then
return
end
currentSecond = mn.getMissionTime ()
-- Make sure the game has moved by X amount of seconds
if (currentSecond < (lastFrameTime+.01)) then
return
end
for loop = 1, #animationQueue do
anim = animationQueue [loop]
-- Make sure the subsystem still exists
if not (anim.subsystem:isValid ()) then
table.remove (animationQueue, loop)
break
end
ProcessAnimation (anim)
-- Decrement the amount of time the animation has left to live
anim.runTime = anim.runTime - .01
if (anim.runTime <= 0) then
-- Animation must be done by now.. deleting it
table.remove (animationQueue, loop)
break
end
end
lastFrameTime = currentSecond
]
#End
Edit: Hmmm it looks like Orientation does return a local angle... If I grab the p,b & h from a subsystem before it starts rotating it's 0,0,0. But it rotates like it's rotating on the parent objects axis instead.
double edit: For the fun of it I put in the ship space angles in and the results were definitely not right! So I'm not sure what exactly is going wrong.
triple edit: Just to see where the rotation goes, I rotated one of the bay doors into a completely different orientation, but the script is still applying the same rotation as before, so it's definitely not using the subobjects local.
-
You might understand this better than I can but here's some testing:
The subsystem (missilebayportdoor01) world rotation is (8.62, 0, 0) local is obviously (0,0,0).
When I rotate it (0,-90,0) I get this:
(http://img.photobucket.com/albums/v356/Shodan_AI/rotatebug.jpg)
The y rotation is correct (-90), but on the other axis it's incorrect.
Now if I enter 8.62 into the other two axis then it works perfectly. I can understand why you would need an offset for one of the axis, but for both?!?
-
Some more testing. I added drawstring debugger info watching the p,b & h for it as it opened up. The numbers went from 0 to down -90 for banking and stayed 0 for the rest. This is how it should work... only the banking should change on the local axis. There must be something I'm completely missing or just not aware of.
-
i think this is a common problem when using euler angles. the problem of gimbal lock. if you rotate the subsystem 90 degrees on the z axis for example, and then want to pitch it up, you would end up yawing to one side because the frame of reference has changed (its been rotated 90 degrees). what happened here is you started at an orientation that is already rotated 8.62 degrees on the pitch axis. you want to rotate about that non-orthagonal axis represented by the initial rotation, not the z axis, but when you rotate by 0'90'0 your rotating about the z axis, resulting in what happened in the image. my instincts are telling me to take the desired orientation of 0'90'0 and matrix multiply it by the transpose of the original orientation of 8.62'0'0. im not totally sure if thats right, the multiply order may be wrong or may be transposing the wrong thing (or maybe you dont need the transpose). but the idea is to put your desired rotation into the initial frame of reference. then just use the resulting orientation in place of 0'90'0.
-
Solved that problem ughhh....
Also... are there ANY debugging tools that come with this? drawString is getting very annoying after a while. I'd kill for a gui debugger right about now lol.
-
run in a window with a debug build and just use ba.print(). you can print anything to the debug console. i just wish you could do the same with a release build. if i ever get around to doing that keyframe animation script i want to do i will probibly also include an in-game animation editor in the lab.
-
Here's a bit of info. Before I start the animation sequence I store the Orientation matrix into a table.
Then I spit the table out to the screen (drawstring)
here's what it looks like (math.deg):
57.29 0 0
0 57.29 0
0 0 57.29
Model is (8.62,0,0) not sure where the 57.29 is coming from.
also non-deg version (i assume in radians) is
1 0 0
0 1 0
0 0 1
edit:Hmmm this also happens when i look at the ships orientation matrix (which is also 0,0,0) but it does change as i rotate the ship around
-
thats what you call an identity matrix. it doesnt contain any rotation, this is the same as 0'0'0. isn't linear algebra fun! :D
-
thats what you call an identity matrix. it doesnt contain any rotation, this is the same as 0'0'0. isn't linear algebra fun! :D
Ahhhh I was wondering if it was the indentity. LOL I barely remember linear algebra from years ago. Also I remember doing some reading on euler angles and said screw it LOL
Also [1][2] and [2][1] will change from zero to values as you rotate (i assume rotational values for each axis)
Ok I'm going to do the destinationOrientation * Transpose (worldAxisOrientation) and see what that produces. There is a slight problem I can already forsee. I don't see a way of seeing the (8.62,0,0) ingame. Ingame the subsystem shows up as (0,0,0), it's local axis
edit: oh oh
__mul: Argument 2 is an invalid type 'table'; type 'orientation' expected
function Transpose( m )
local newOrient = {}
newOrient[1] = m[1]
newOrient[2] = m[4]
newOrient[3] = m[7]
newOrient[4] = m[2]
newOrient[5] = m[5]
newOrient[6] = m[8]
newOrient[7] = m[3]
newOrient[8] = m[6]
newOrient[9] = m[9]
return newOrient
end
tranpose = Transpose (ba.createOrientation (8.63,0,0))
newDest = destOrientation * transpose
Is there anyway to do a orientation constructor or have newOrient be declared as an orientation?
-
most porgrammers consider euler angles evil, prefering matrices or quaternions instead. the only reason they are used at all is because most users and artists understand that system.
ba.createOrientation() can take 0, 3 or 9 parameters. with no parameters it creates an identity matrix, with 3 it assumes its a euler angle, with 9 it takes a full matrix. reguardless you end up with an orientation object which is just a 3x3 matrix internally. instead of making your own function i think you can use orientation:getTranspose() to transpose a matrix. tables generally dont have operators defined (but you can if you set a metatable, but thats a different discussion).
this should be allowed.
newDest = destOrientation * ( ba.createOrientation(8.63,0,0):getTranspose() )
-
Unfortunately neither dest*ba.create nor ba.create*dest work. Both cause the door to excessively spin around on the wrong axis (z)
I've stepped back and starting working with a much simpler model. So far I've noticed that when I rotate on one axis, it rotates based on the world (ship) axis not the subsystem. Two when I rotate are more than one axis, things tend to go wrong, I'm guessing that euler issue. Will report back when I've found more info.
edit: Third, the subsystem.Orientation p/b/h all report 0. So subsystem.Orientation must be based on it's local axis-space.
-
Another test....
Here's the sample code:
if (waitStep == 0) then
animation.subsystem.Orientation = CreateOrientation (45,0,0)
return true
end
if (waitStep == 1) then
animation.subsystem.Orientation = CreateOrientation (0,90,0)
return true
end
waitStep is changed via keypress, that way you only go through the animation sequence once per press. First time it moves the object to world (0,0,0) then rotates it "open"
Here's how the test model is setup, the "door" is -45,0,0 (world)
(http://img.photobucket.com/albums/v356/Shodan_AI/bad1.jpg)
Here's after the first keypress (45,0,0), Looks good!
(http://img.photobucket.com/albums/v356/Shodan_AI/bad2.jpg)
And after rotating it 0,90,0... errr something happened bad!
(http://img.photobucket.com/albums/v356/Shodan_AI/bad3.jpg)
-
the problem here is we need to rotate a thing about an arbitrary axis. heres how you build an arbitrary axis rotation matrix:
function arbitraryAxisMatrix(w,x,y,z)
local cosi = math.cos(w)
local sine = math.sin(w)
local tang = 1 - cosi
local aarot = ba.createOrientation( tang * x^2 + cosi, tang * x * y + sine * z, tang * x * z - sine * y,
tang * x * y - sine * z, tang * y^2 + cosi, tang * y * z + sine * x,
tang * x * z + sine * y, tang * y * z - sine * x, tang * z^2 + cosi )
return aarot
end
i havent tested this with freespace though, the math was pulled from my quaternion library (which was likewise untested) for nukesim. so im not sure if the handedness is the same. but x,y,z is a normalized vector which is the axis of rotation, and w is the rotation about that axis (essentially a quaternion). returns an orientation object. in your case the axis would be parallel to the hinge on your missile door.
-
Hey I think I'm making some progress...
It's gaudy and inefficient:
Let's say I want to rotate that 90 so i looks like it opened up
temp = arbitraryAxisMatrix (math.rad(45), 1,0,0) --45 90 0
temp = arbitraryAxisMatrix (math.rad (90), 0,0,1) * temp
temp = arbitraryAxisMatrix (math.rad (-45), 1,0,0) * temp
Looks like you have to rotate the object back down to the worlds (0,0,0), apply the rotation you want then reset it back to the original local rotation
-
Oh... I understand how to take something originally rotated (-45,0,0) to (0,0,0) -> (math.rad(45),1,0,0)
how do I do something that's rotated say (-45,90,0)?
Do I have to do this:
(math.rad(45),1,0,0) * (math.rad(-90),0,1,0) [or reverse whatever it happens to be]
?
-
never hurts to try it. thats my usuall aproach, to tweak the math till it works. if that doesnt work, another thing you can try is you can transform the xyz vector for the second matrix by the first matrix (use orientation:unrotateVector()) and then use the transformed vector in place of the original vector.
mat1 = arbitraryAxisMatrix(math.rad(45),1,0,0)
vec = mat1:unrotateVector(ba.createVector(0,1,0))
mat2 = arbitraryAxisMatrix(math.rad(-90),vec.x,vec.y,vec.z)
mat3 = mat1*mat2
of course im not sure if that will do it. the first matrix stores the 45 degree rotation. along the x axis.
the vector for the second matrix is then rotated into the space represented by the first matrix.
this axis is then used for the second matrix. im not quite sure whether mat2 or mat3 is the one you need.
-
For the moment I'll stick with what I can understand :D
However there seems to be a small problem, at the 90 degree the object "flickers"/switches around axis then resumes normal motion afterwards. I'm guessing something to do with sin/cos(90)?
I'm going to step by step from about 88 to 92 and look at the matrix entries.
edit:
I've narrowed it down to just that matrix that does the 90 degree mark arbitraryAxisMatrix (math.rad(p),0,0,1)
At 89:
.99 57 0
-57 .99 0
0 0 57
At 90:
~0 57 0
-57 ~ 0 0
0 0 57
Everything works out fine in the excel worksheet. I wonder if it's something in the matrix multiplication i don't understand, because cos(x) looks good. 89 = .017 90 = 0 91 = -.017
-
flicker is usually caused by a singularity. your better off interpolating across one of those than animating the angles.
matrix multiply can either be row-column or column-row, and also there are left handed and right handed systems. i dont really have much time to go into this, i have to catch a plane in about 30 minutes, but heres a good site for understanding all the game math you would ever use: http://www.euclideanspace.com/maths/algebra/matrix/index.htm
-
Some good news... if the subsystem origins are at (0,0,0) world then everything works great. Now if you adjust the subystems rotation as part of the pof, when you go to rotate it tends to wobble, not the wobble because of 90 degree stuff.
Now gotta see what's causing that.
edit: Hmmm I wonder if it's because I'm modifying the same axis twice in the matrix (first to reset to 0,0,0, then to get the new angle)
double edit:Ugh I don't know what I was doing before. Whatever it was was a heck of a lot more work than what was needed. Apparently the subsystems (0,0,0) is the actual subsystems (0,0,0) [not the ships (0,0,0)]. This makes things 75% easier. Need to do more testing but this looks very hopeful!
-
Hmmm I've just learned something, makes things a bit more difficult.
Let's say the subobject has world orientation of (0,0,0) [for simplisitic sake]
Now let's rotate it (-90,0,0). It looks good. but now let's look at the p/h/b and this is what we get: p=88 h~=180 b~=180
So now let's derotate it (90,0,0). Things get all goofed up
edit: Hmmm.. think I might have to have SetupAnimation to include the starting orientation and destination orientation to work right.
-
if you have a starting orientation that is good (door looks closed), and an ending orientation that is good (door looks open), you should just be able to interpolate between them in theory (and assuming the interpolation algorithm is singularity free and fully linear, such as with slerp).
-
Partly because of this:
88,0,0
89,0,0
90.1,180,180
91,180,180
if you want to move from (89,0,0) to (0,0,0) [(89,0,0)-(0,0,0) = (89,0,0)]
but: (91,180,180) to (0,0,0) [(91,180,180)] breaks rotation, and i can't tell if it's just because of the rotation that's caused or if it's a legit 180 rotation
but anyways the start orientation and end orientation works pretty good. But I have noticed something a bit odd.... if you rotate on more than one axis, sometimes one axis will get ahead of the other then the other will quickly catch up. Not really sure what's causing that, the delta's do not change
local newP = animation.originalOrientation.p + animation.deltaP * animation.loop
Outside of that, I don't see any other bugs so far. :yes:
-
Ugh one massive bug just bit me.... when the local axis != world axis and you try to rotate on certain axises you get rotations that are broken.
For example, let's say you have an object rotate world axis:(-45,0,0) local:(0,0,0) then you rotate by local:(0,-90,0) to make it appear as if it was opening (like a bay door). Except it pivots on the world axis so instead of opening like it's on hinges it swing around. What makes it really weird is local angle (0,0,0) is still on the subsystems local origin, which is completely confusing.
edit: Hmm, one possible if not ugly solution is to force all animated subobjects to be oriented (0,0,0) [world], then use an "init" function to rotate them to the correct starting positions
double edit: ok after two code rewrites I threw away my attempt and started using your code. So far world=local axis rotation works good, but when they don't match things get messed up. It keeps on rotating on the world axis. I'll have to look at it better latter.
-
Ha ha... I kinda got something working. It's a bit awkward, but it works.
-
good to here you got it working, i was running out of ideas. only other thing was to delve into quaternions, which im not too good with thus far. a slerp interpolation, which only works with quats, would give better interpolation. its kind of like walking across the surface of a 4 dimensional sphere, and im kinda worried that my head would explode if i go there. heres some code for slerp (at least i hope this is, i based this off some c code i found on the internet).
--slerp interpolare between qa* and qb* by interpolation value int
function slerp(qaW, qaX, qaY, qaZ, qbW, qbX, qbY, qbZ, int)
local cosHalfTheta = qaW * qbW + qaX * qbX + qaY * qbY + qaZ * qbZ
--handle inverted quats
if cosHalfTheta < 0 end
qbW = -qbW
qbX = -qbX
qbY = -qbY
qbZ = qbZ
cosHalfTheta = -cosHalfTheta
end
--if qa=qb or qa=-qb then theta = 0 and we can return qa
if math.abs(cosHalfTheta) >= 1.0 then
return qaW, qaX, qaY, qaZ
end
--Calculate temporary values.
local halfTheta = math.acos(cosHalfTheta)
local sinHalfTheta = math.sqrt(1.0 - cosHalfTheta*cosHalfTheta)
--if theta = 180 degrees then result is not fully defined
--we could rotate around any axis normal to qa or qb
local w,x,y,z
if math.abs(sinHalfTheta) < 0.001 then
w = (qaW * 0.5 + qbW * 0.5)
x = (qaX * 0.5 + qbX * 0.5)
y = (qaY * 0.5 + qbY * 0.5)
z = (qaZ * 0.5 + qbZ * 0.5)
return w,x,y,z
end
local ratioA = math.sin((1 - int) * halfTheta) / sinHalfTheta
local ratioB = math.sin(int * halfTheta) / sinHalfTheta
--calculate Quaternion.
w = (qaW * ratioA + qbW * ratioB)
x = (qaX * ratioA + qbX * ratioB)
y = (qaY * ratioA + qbY * ratioB)
z = (qaZ * ratioA + qbZ * ratioB)
return w,x,y,z
end
note that this takes quaternions (w,x,y,z) and not eulers. and just plug the return values into the arbitrary axis function to create a matrix that you can actually use from the result. you may need this to convert eulers to quaternions as well:
function eulerToQuat(p,y,r)
local c2 = math.cos(p/2)
local s2 = math.sin(p/2)
local c1 = math.cos(y/2)
local s1 = math.sin(y/2)
local c3 = math.cos(r/2)
local s3 = math.sin(r/2)
local c1c2 = c1*c2
local s1s2 = s1*s2
local W = c1c2*c3 - s1s2*s3
local X = c1c2*s3 + s1s2*c3
local Y = s1*c2*c3 + c1*s2*s3
local Z = c1*s2*c3 - s1*c2*s3
return W,X,Y,Z
end
ive never tested either of these so dont know if they are right. p,y,r is pitch, yaw and roll (which may be different order from freespace, im not sure). so essentially create two quaternions from your euler angles, then interpolate between them with slerp, and finally convert them to matrix with arbitraryAxisMatrix(). but this is kind of unfamiliar territory for me and none of the code has been tested.
-
Yikes.. looks nasty lol
basically right now, the subobject's axis must match the worlds. Just call an Initialize function that'll rotate it to the desired location. Then you can call the rotate animations whenever you want. It still needs some work and lot of testing...
-
Ugh... for lua being a nice dynamic language.. it's taking longer to do stuff than C# :ick:
Where the heck is variable declarations and classes? :hopping:
-
i kinda wrote a tutorial for that:
http://www.hard-light.net/forums/index.php/topic,45303.0.html
granted im a lot better with this stuff now than when i wrote it. but really when it comes right down to it is to use tables as your object, and assign a metatable which lets you overload operators and the like. tutorial should cover the basics of operator overloading and member functions.
e:
i guess i hadn't figured out member functions yet when i wrote it. i may give the tutorial a part 2. but essentially you create a table which contains member functions (i call it a metamethods table) and then in your metatable you just set __index = metamethodstable. i usually have it set up like:
--metamethods
aclass_me = {}
aclass_me:getX()
return self.x
end
aclass_me:setX(newX) --lol
self.x = newX
end
--metatable (bare minimum to make member funcs work)
aclass_mt = {
--this makes member funcs work
__index = aclass_me
}
--constructor
aclass(X)
a = {}
if X then
a.x = X
else
a.x = 0
end
return setmetatable(a, aclass_mt)
end
--example
foo = aclass()
bar = foo:getX()
foo:setX(bar^2)
should point out that this isnt as strict as classes in language like c# or c++, there are countless variations.
-
I've kinda gotten back into the ole' C style object's being passed as params to function style.
Oh and Polish notation for naming variables :P
-
Demo example! ignore the stuttering at the beginning, that's fraps.
https://www.youtube.com/watch?v=B_SnBfShjT4&feature=youtu.be
-
Looks splendid.
-
For now the subsystem's axis must match that of the ships. In the F3 viewer they will look off, but ingame it automatically sets them up properly. I'm making it easy enough to code the subsystems in though:
local Closed = CreateOrientation (0,0,0)
local ZeroOffset = Closed
local OpenPort = CreateOrientation (0, math.rad (-90), 0)
local OpenStar = CreateOrientation (0, math.rad ( 90), 0)
RegisterLandingGearSubsystem (p_subsystemList, "FrontLandingGear", ZeroOffset, Closed, CreateOrientation (math.rad(-90), 0, 0), 1.5, 1.5 )
RegisterLandingGearSubsystem (p_subsystemList, "PortMainLandingGear", ZeroOffset, Closed, OpenPort, 1.5, 1.5)
RegisterLandingGearSubsystem (p_subsystemList, "StarMainLandingGear", ZeroOffset, Closed, OpenStar, 1.5, 1.5)
RegisterLandingGearSubsystem (p_subsystemList, "PortDoor01", ZeroOffset, Closed, OpenPort, .5, 2.5)
RegisterLandingGearSubsystem (p_subsystemList, "PortDoor02", ZeroOffset, Closed, OpenPort, .5, 2.5)
RegisterLandingGearSubsystem (p_subsystemList, "PortDoor03", ZeroOffset, Closed, OpenPort, .5, 4.5)
RegisterLandingGearSubsystem (p_subsystemList, "StarDoor01", ZeroOffset, Closed, OpenStar, .5, 2.5)
RegisterLandingGearSubsystem (p_subsystemList, "StarDoor02", ZeroOffset, Closed, OpenStar, .5, 2.5)
RegisterLandingGearSubsystem (p_subsystemList, "StarDoor03", ZeroOffset, Closed, OpenStar, .5, 4.5)
That's pretty much all you have to do, just register the subsystems with the script and when you have FRED call "OpenLandGear" it'll know which subsystems to rotate and do it all by itself. Same with closing them. Right now I'm working on the thrust vector part. Working pretty good except for one small part, when you rotate from a little less than 359 to a little more than 1, it gets messed up. Thinks it's delta changed 300+ degrees in a frame. I also want to do a sweeping motion (like on real jet radio systems) and thrust nozzle sizing.
Ugh... ship pitch rotation goes as follows:
{0.0001......89.99999, -89.99999....-0.0001,.0001......89.99999, -89.99999....-0.0001} for a complete 360 loop. Not sure how to handle this. 89 - (-89) != 2
-
transform a 0'0'1 vector by the matrix, then use atan2 on its components (the two that are not the axis you want the rotation for). theres probibly a better way to do this but idk.
-
I'm going to do some if-then around the 90 degree point. I'll post it when I get it working.
edit: i was wrong, probably why my methods were failing big time
it's:
0,10,20,30.....70,80,89,89,80,70,......30,20,10,0,-10,-20,-30.....-80,-89,-89,-80,-70....-20,-10,0
back to scratchpaper to find the solution
-
What are those numbers supposed to mean? Are they angles?
Nuke, what are you doing :ick: The beauty of quaternions is that you can slerp just by lerping each component separately.
-
What are those numbers supposed to mean? Are they angles?
Nuke, what are you doing :ick: The beauty of quaternions is that you can slerp just by lerping each component separately.
i said it was untested and probibly didnt work :D, care to show me something that does?
-
Ya that's the pitch of the craft as it does a loop-de-loop. For some reason I'm getting sudden negative flips on the delta pitches between two frames. i.e. 1.2, 1.2,1.1,1.2,1.2,1.1,1.2,-2,-2.1,-1,9....
edit: i suppose I could watch for neg/pos change on an angle that's too large for normal use.
something like:
if (sign(delta) != sign (previousDelta)
changeDelta = delta - previousDelta
if (changeDelta > TOO_BIG)
-
I think I see the problem, just can't seem to see a solution
source - dest = delta
75 - 80 = -5
80 - 85 = -5
85 - 90 = -5
90 - 85 = 5
even though your still rotating at the same degrees per second in the same direction.
I can't check H or B because the ship might actually be banking, rotating in those directions too.
-
We need more people giving him conflicting suggestions!
subsystem.Orientation is relative to the ship, right? Maybe try this:
function AddAnimationToQueue(ship, subsystem, destOrientation, ttl)
local now = mn.getMissionTime()
local animation =
{
ship = ship,
subsystem = subsystem,
startOri = EulerToQuaternion(subsystem.Orientation), -- idk the implementation for this (never used euler angles)
endOri = EulerToQuaternion(destOrientation),
startTime = now,
endTime = now + ttl
}
table.insert(animationQueue, animation)
end
function ProcessAnimation(animation)
-- calculate lerp coefficients
local b = (mn.getMissionTime() - animation.startTime) / (animation.endTime - animation.startTime)
local a = 1.0 - b
local slerped =
{
w = animation.startOri.w * a + animation.endOri.w * b,
x = animation.startOri.x * a + animation.endOri.x * b,
y = animation.startOri.y * a + animation.endOri.y * b,
z = animation.startOri.x * a + animation.endOri.z * b
}
animation.subsystem.Orientation = QuaternionToEuler(slerped) -- idk this one either
end
Big differences: instead of storing a TTL and deltas, it stores a start and end time and orientation. And orientations are converted to quaternions to make slerp nicer. Er... do you have quat-to-euler and euler-to-quat? If not you can probably find those online somewhere.
-
Heres what I have right now. My current problem lies in ProcessContinuousAnimation, the deltaOrientation.p
#Conditional Hooks
$Application: FS2_Open
$On Game Init:
[
--shipAnimationList:
-- Ship - the ship data
-- ShipPreviousOrientation - orientation of the ship during the last frame (used to get the delta angle)
-- PreviousDelta
-- DeltaSignage
-- SubsystemList
-- Name - name of subsystem
-- Offset - offset orientation
-- CurrentOrientation - current orientation of subsystem
-- Active - if subsystem is currently being rotated
-- Type - type of animation, see below:
-- OpenTime - Time for animation to preform for opening landing gear and missile bays
-- CloseTime - Time for animation to preform for closing landing gear and missile bays
-- Closed - Closed position for landing gear and missile bays
-- Opened - Open position for landing gear and missile bays
-- VectorFactor - Vector direction for thrust vectoring
-- MinPosition - Minimum position for thrust vectoring
-- MaxPosition - Max position for thrust vectoring
-- Animation Types:
-- 0 - Landing gears
-- 1 - Missile Bays
-- 2 - Sweeping motion
-- 3 - Thrust Vectoring
-- 4 - Thrust Nozzles
LANDING_GEAR = 0
MISSILE_BAY = 1
SWEEPING = 2
THRUST_VECTOR = 3
THRUST_NOZZLE = 4
--animationQueue:
-- ship - Actual ship
-- subsystem - Actual subsystem
-- destOrientation - Destination orientation
-- runtime - Time to do the rotation
-- originalOrientation - Original orientation of subsystem before rotation begins
-- startTime - When the rotation started
-- endTime - When the rotation will end
--Variable Prefixes:
-- g_ = global
-- p_ = private (don't touch unless absolutely necessary!) Also should only be "p_" when
-- using public functions
--Variable PostFixes:
-- List = list/array
-- Data = Physical data (passed to via game engine)
-- Record = Table record (lua table)
g_b = 666
g_h = 666
g_p = 666
g_time = 0
g_percent = 666
g_deltaH = 666
g_deltaB = 666
g_lenp = 0
g_lenb = 0
g_lenh = 0
g_orientNow = {}
g_pp = 0
debugtest = 0
g_test1 = 0
g_test2 = 0
g_prep = false
g_doOnce = false
g_lastFrameTime = 0
animationQueue = {}
shipAnimationList = {}
-----------------------------------------------------------------------------------------------
-- Your ship registeration function goes here aka pure virtual functions
-----------------------------------------------------------------------------------------------
-- Just call one of the Register subsystem functions with p_subsystemList, name of the subsystem
-- and it's necessary info
-- Params:
-- p_subsystemList - List of all subsystems on that ship,
-- do not modify just forward to RegisterSystems
-----------------------------------------------------------------------------------------------
function RegisterBearcat (p_subsystemList)
--RegisterLandingGearSubsystem (p_subsystemList, "MissileBayPortDoor01", CreateOrientation (math.rad (8.62),0,0), CreateOrientation (0,0,0), CreateOrientation (0, math.rad (-90), 0), 2, 2)
--RegisterLandingGearSubsystem (p_subsystemList, "MissileBayStarDoor01", CreateOrientation (math.rad (8.62),0,0), CreateOrientation (0,0,0), CreateOrientation (0, math.rad (90), 0), 2, 2)
local Closed = CreateOrientation (0,0,0)
local ZeroOffset = Closed
local OpenPort = CreateOrientation (0, math.rad (-90), 0)
local OpenStar = CreateOrientation (0, math.rad ( 90), 0)
--RegisterLandingGearSubsystem (p_subsystemList, "FrontLandingGear", ZeroOffset, Closed, CreateOrientation (math.rad(-90), 0, 0), 1.5, 1.5 )
--RegisterLandingGearSubsystem (p_subsystemList, "PortMainLandingGear", ZeroOffset, Closed, OpenPort, 1.5, 1.5)
--RegisterLandingGearSubsystem (p_subsystemList, "StarMainLandingGear", ZeroOffset, Closed, OpenStar, 1.5, 1.5)
--RegisterLandingGearSubsystem (p_subsystemList, "PortDoor01", ZeroOffset, Closed, OpenPort, .5, 2.5)
--RegisterLandingGearSubsystem (p_subsystemList, "PortDoor02", ZeroOffset, Closed, OpenPort, .5, 2.5)
--RegisterLandingGearSubsystem (p_subsystemList, "PortDoor03", ZeroOffset, Closed, OpenPort, .5, 4.5)
--RegisterLandingGearSubsystem (p_subsystemList, "StarDoor01", ZeroOffset, Closed, OpenStar, .5, 2.5)
--RegisterLandingGearSubsystem (p_subsystemList, "StarDoor02", ZeroOffset, Closed, OpenStar, .5, 2.5)
--RegisterLandingGearSubsystem (p_subsystemList, "StarDoor03", ZeroOffset, Closed, OpenStar, .5, 4.5)
RegisterThrustVectorGearSubsystem (p_subsystemList, "PortEngine", CreateOrientation (0,0,0), CreateOrientation (-10,0,0), CreateOrientation (math.rad (-90),0,0), CreateOrientation (math.rad (90),0,0))
end
-----------------------------------------------------------------------------------------------
-- Protected Methods, Update as needed (aka override functions)
-----------------------------------------------------------------------------------------------
-- Intializes all of the ship classes rotations
-- Update this function when adding new ship classes
-- Params: shipData - Ship to be initialized
-- Returns: New ship data ready to be added to the list
function InitializeShipRotation (shipData)
local newShipRecord = {}
newShipRecord.Ship = shipData
newShipRecord.SubsystemList = {}
newShipRecord.ShipPreviousOrientation = shipData.Orientation
newShipRecord.PreviousDelta = CreateOrientation (0,0,0)
newShipRecord.DeltaSignage = 1
if (shipData.Class.Name == "F-104D Bearcat") then
RegisterBearcat (newShipRecord.SubsystemList)
end
--*************************************************
-- Add different ship classes as needed here
--*************************************************
-- Any subsystem that wasn't defined gets the simple (0,0,0) world rotation
SetRestAsSimple (newShipRecord.SubsystemList, shipData)
SetInitialOrientation (newShipRecord)
return newShipRecord
end
-----------------------------------------------------------------------------------------------
-- Public functions that should be called by FRED
-----------------------------------------------------------------------------------------------
function OpenLandGear(shipData)
OpenLandingGear (shipData)
end
function CloseLandGear(ship)
CloseLandingGear (ship)
end
-----------------------------------------------------------------------------------------------
-- Functions that should be called (aka public) but not modified (aka final)
-----------------------------------------------------------------------------------------------
-- Registers a subsystem into the ship's subsystem table
-- Params: subsystemList - list of all subsystems for that ship
-- name - Name for new subsystem
-- offsetOrientation - Orientation that subsystem should initialy have
function RegisterSubsystem (subsystemList, name, offsetOrientation)
local newEntryRecord = {}
newEntryRecord.Name = name
newEntryRecord.Offset = offsetOrientation
newEntryRecord.CurrentOrientation = CreateOrientation (0,0,0)
newEntryRecord.Active = false
table.insert (subsystemList, newEntryRecord)
end
-- Registers a subsystem as a landing gear into the ship's subsystem table
-- Params: subsystemList - list of all subsystems for that ship
-- name - Name for new subsystem
-- offsetOrientation - Orientation that subsystem should initialy have
-- closedPosition - Closed position for the landing gear
-- openPosition - Opened position for the landing gear
-- openTime - Time it takes to open
-- closeTime - Time it takes to close
function RegisterLandingGearSubsystem (subsystemList, name, offsetOrientation, closedPosition, openPosition, openTime, closeTime)
local newEntryRecord = {}
newEntryRecord.Name = name
newEntryRecord.Offset = offsetOrientation
newEntryRecord.CurrentOrientation = CreateOrientation (0,0,0)
newEntryRecord.Active = false
newEntryRecord.Type = LANDING_GEAR
newEntryRecord.Closed = closedPosition
newEntryRecord.Open = openPosition
newEntryRecord.OpenTime = openTime
newEntryRecord.CloseTime = closeTime
table.insert (subsystemList, newEntryRecord)
end
-- Registers a subsystem as a thrust vectoring into the ship's subsystem table
-- Params: subsystemList - list of all subsystems for that ship
-- name - Name for new subsystem
-- offsetOrientation - Orientation that subsystem should initialy have
-- closedPosition - Closed position for the landing gear
-- openPosition - Opened position for the landing gear
-- openTime - Time it takes to open
-- closeTime - Time it takes to close
function RegisterThrustVectorGearSubsystem (subsystemList, name, offsetOrientation, shipDeltaFactor, minPosition, maxPosition)
local newEntryRecord = {}
newEntryRecord.Name = name
newEntryRecord.Offset = offsetOrientation
newEntryRecord.CurrentOrientation = CreateOrientation (0,0,0)
newEntryRecord.Active = false
newEntryRecord.Type = THRUST_VECTOR
newEntryRecord.VectorFactor = shipDeltaFactor
newEntryRecord.MinPosition = minPosition
newEntryRecord.MaxPosition = maxPosition
table.insert (subsystemList, newEntryRecord)
end
-- Create the orientation matrix, just to create cleaner code
-- Params: X - X Coordinate
-- Y - Y Coordinate
-- Z - Z Coordinate
-- Returns: Orientation matrix
function CreateOrientation (x, y, z)
return ba.createOrientation (x,y,z)
end
-----------------------------------------------------------------------------------------------
-- Private functions pertaining to the animationShipList table and children
-----------------------------------------------------------------------------------------------
-- Retrieves the ship's subsystem animation list, note this is not the actual subsystems, but
-- rather the animation orientation list
-- Returns: List of subsystem's orientations (look at shipAnimationList for details)
function GetShipSubsystems (shipName)
for loop = 1, #shipAnimationList do
if (shipAnimationList[loop].Ship.Name == shipName) then
return shipAnimationList[loop].SubsystemList
end
end
end
-- Retrieves the subsystem's information about orientation
-- Returns: List of subsystem's orientation information (look at shipAnimationList->SubsystemList for details)
function GetSubsystemData (subsystems, subsystemName)
for loop = 1, #subsystems do
if (subsystems[loop].Name == subsystemName) then
return subsystems[loop]
end
end
end
-- Retrieves the ships's animation/orientation information
-- Returns: Ship's orientation information (look at shipAnimationList->SubsystemList for details)
function GetShipData (shipName)
for loop = 1, #shipAnimationList do
if (shipAnimationList[loop].Ship.Name == shipName) then
return shipAnimationList[loop]
end
end
end
-- Ship no longer exists , remove it from the tables,
-- needs testing
function RemoveLostShips ()
gr.drawString ("ship left", 10, 10)
for loop = 1, #shipAnimationList do
local stillExists = false
local storedShip = shipAnimationList[loop]
for innerLoop = 1, #mn.Ships do
if (storedShip.Ship.Name == mn.Ships[innerLoop].Name) then
stillExists = true
break
end
end
if (stillExists == false) then
table.remove (shipAnimationList, loop)
return
end
end
end
-- Adds any new ships to the animation list
function AddNewShip ()
-- Go through all the ships currently in the mission
for loop = 1, #mn.Ships do
local existingName = mn.Ships[loop].Name
local found = false
if (#shipAnimationList ~= 0) then
-- Now go through all the ships currently in the animation list
for innerLoop = 1, #shipAnimationList do
local recordName = shipAnimationList[innerLoop].Ship.Name
if (existingName == recordName) then
-- This ship already exists in the list, skip it
found = true
break
end
end
end
if (found == false) then
-- Found one that doesn't exist in the orientation list, let's add it
local shipRecord = InitializeShipRotation (mn.Ships[loop])
table.insert (shipAnimationList, shipRecord)
end
end
end
-- Updates the orientation table/list to reflect any changes in the ships in the mission
function UpdateShipList ()
if (#shipAnimationList > #mn.Ships) then
-- Something got destroyed
RemoveLostShips ()
else
-- Something new was added/arrived
AddNewShip ()
end
end
-- When a subsystem has an orientation rotation, we must save the ending orientation for future use
-- Params: subsystemRecord - Current animation info about the subsystem, look at animationQueue for details
function FinishedSubsystemAnimation (subsystemRecord)
local subsystems = GetShipSubsystems (subsystemRecord.shipData.Name)
local subsystemData = GetSubsystemData (subsystems, subsystemRecord.subsystem.Name)
subsystemData.CurrentOrientation = subsystemRecord.destOrientation
subsystemData.Active = false
end
-- When a subsystem's offset orientation isn't registered, use (0,0,0) as a default value
function SetRestAsSimple (subsystemList, shipData)
for loop = 1, #shipData do
local subsystem = shipData[loop]
local found = false
-- Run through all the ships subsystems
for innerloop = 1, #subsystemList do
local recordedSubsystem = subsystemList[innerloop]
if (recordedSubsystem.Name == subsystem.Name) then
-- Found one that wasn't registered
found = true
break
end
end
if (found == false) then
RegisterSubsystem (subsystemList, subsystem.Name, CreateOrientation (0,0,0))
end
end
end
-- Sets the subsystems initial orientation, based on was it was registered with
-- Params: shipRecord - ship to have it's subsystems initialized
function SetInitialOrientation (shipRecord)
for loop = 1, #shipRecord.SubsystemList do
local offset = shipRecord.SubsystemList[loop].Offset
local subsystemName = shipRecord.SubsystemList[loop].Name
shipRecord.Ship[subsystemName].Orientation = offset
shipRecord.SubsystemList[loop].CurrentOrientation = offset
end
end
-- Open the landing gear(s)
-- Params: shipName - name of ship
function OpenLandingGear (shipName)
local shipSubsystemList = GetShipSubsystems (shipName)
for loop = 1, #shipSubsystemList do
local currentSubsystem = shipSubsystemList[loop]
if (currentSubsystem.Type == LANDING_GEAR) then
AddAnimationToQueue (mn.Ships[shipName],
currentSubsystem.Name,
currentSubsystem.Open,
currentSubsystem.OpenTime)
end
end
end
-- Close the landing gear(s)
-- Params: shipName - name of ship
function CloseLandingGear (shipName)
local shipSubsystemList = GetShipSubsystems (shipName)
for loop = 1, #shipSubsystemList do
local currentSubsystem = shipSubsystemList[loop]
if (currentSubsystem.Type == LANDING_GEAR) then
AddAnimationToQueue (mn.Ships[shipName],
currentSubsystem.Name,
currentSubsystem.Closed,
currentSubsystem.CloseTime)
end
end
end
-- Creates a new animation entry and adds it to the animation queue
-- Params: shipName - Name of ship in the mission
-- subsystemName - Name of subsystem found on shipName that needs to be rotated
-- originalOrientation - Orientation the object starts in (if opening a bay door, usually is (0,0,0))
-- destOrientation - Orientation of the subsystem once the animation is finished
-- rotationTime - Total time for the animation
function StartAnimation(shipName, subsystemName, originalOrientation, destOrientation, rotationTime)
AddAnimationToQueue (mn.Ships[shipName], subsystemName, originalOrientation, destOrientation, rotationTime)
end
-- Adds an animation to the animation queue
-- Params: shipData - ship that has the subsystem to animation
-- subsystemName - Subsystem that needs to be rotated
-- originalOrientation - Orientation the object starts in (if opening a bay door, usually is (0,0,0))
-- destOrientation - Orientation of the subsystem once the animation is finished
-- timeForAnimation - Total time for the animation
function AddAnimationToQueue (shipData, subsystemName, destOrientation, timeForAnimation)
local subsystems = GetShipSubsystems (shipData.Name)
local subsystem = GetSubsystemData (subsystems, subsystemName)
subsystem.Active = true
-- Grab the subsystem's current orientation
local subsystemOffset = CreateOrientation (0,0,0)
local subsystemCurrentOrientation = CreateOrientation (0,0,0)
subsystemOffset = subsystem.Offset
subsystemCurrentOrientation = subsystem.CurrentOrientation
local updatedestOrientation = CreateOrientation (0,0,0)
updatedestOrientation.p = destOrientation.p + subsystemOffset.p
updatedestOrientation.h = destOrientation.h + subsystemOffset.h
updatedestOrientation.b = destOrientation.b + subsystemOffset.b
local newShipRecord = {}
newShipRecord.shipData = shipData
newShipRecord.subsystem = shipData[subsystemName]
newShipRecord.destOrientation = updatedestOrientation
newShipRecord.runtime = timeForAnimation
newShipRecord.originalOrientation = subsystemCurrentOrientation
newShipRecord.startTime = mn.getMissionTime ()
newShipRecord.endTime = mn.getMissionTime () + timeForAnimation
table.insert (animationQueue, newShipRecord)
end
-- Process one piece of the animation
-- Params: animation - animation Step to process
function ProcessAnimation (animation)
local currentTime = (mn.getMissionTime () - animation.startTime) / animation.runtime
local test1 = animation.originalOrientation:getInterpolated (animation.destOrientation, currentTime)
animation.subsystem.Orientation = test1
end
function ProcessContinuousAnimationForShips ()
for loop = 1, #mn.Ships do
ProcessContinuousAnimation (mn.Ships[loop])
end
end
function Sign (x)
if (x < 0) then
return -1
else
return 1
end
end
g_laptime = 0
function ProcessContinuousAnimation (shipData)
local subsystemList = GetShipSubsystems (shipData.Name)
local animationShipData = GetShipData (shipData.Name)
for loop = 1, #subsystemList do
local subsystem = subsystemList[loop]
if (subsystem.Type == THRUST_VECTOR) then
if (mn.getMissionTime () > g_laptime + 1) then
local vectorFactor = subsystem.VectorFactor
local newOrientation = shipData.Orientation
local deltaOrientation = CreateOrientation (0,0,0)
local previousOrientation = animationShipData.ShipPreviousOrientation
local previousDelta = animationShipData.PreviousDelta
deltaOrientation.p = (newOrientation.p - previousOrientation.p)
if (Sign (deltaOrientation.p) ~= Sign (previousDelta.p)) then
debugtest = "sign change"
local temp = math.abs (deltaOrientation.p) - math.abs (previousDelta.p)
temp = math.abs (temp)
if (temp > math.rad (5)) then
debugtest = "sign changed, very big"
else
debugtest = "signed changed, small"
end
else
debugtest = ""
end
deltaOrientation.b = (newOrientation.b - previousOrientation.b)
deltaOrientation.h = (newOrientation.h - previousOrientation.h)
deltaOrientation.p = deltaOrientation.p * vectorFactor.p
deltaOrientation.b = deltaOrientation.b * vectorFactor.b
deltaOrientation.h = deltaOrientation.h * vectorFactor.h
AddAnimationToQueue (shipData, subsystem.Name, deltaOrientation, 1)
g_deltaP = math.deg(animationShipData.PreviousDelta.p)
g_deltaH = math.deg(deltaOrientation.p)
g_p = math.deg(previousOrientation.p)
g_h = math.deg(newOrientation.p)
animationShipData.PreviousDelta = deltaOrientation
g_laptime = mn.getMissionTime () + .1
end
end
end
animationShipData.ShipPreviousOrientation = shipData.Orientation
end
-----------------------------------------------------------------------------------------------
-- General purpose private functions
-----------------------------------------------------------------------------------------------
-- Nuke's Matrix Creation tool
function arbitraryAxisMatrix(w,x,y,z)
local cosi = math.cos(w)
local sine = math.sin(w)
local tang = 1 - cosi
local aarot = ba.createOrientation( tang * x^2 + cosi, tang * x * y + sine * z, tang * x * z - sine * y,
tang * x * y - sine * z, tang * y^2 + cosi, tang * y * z + sine * x,
tang * x * z + sine * y, tang * y * z - sine * x, tang * z^2 + cosi )
return aarot
end
function DebugSpew ()
gr.drawString ("p", 950, 10)
gr.drawString (g_p, 1000, 10)
gr.drawString ("h", 950, 50)
gr.drawString (g_h, 1000, 50)
gr.drawString ("b", 950, 100)
gr.drawString (g_b, 1000, 100)
--gr.drawString (g_time, 1000, 150)
gr.drawString ("deltaP", 1200, 10)
gr.drawString ((g_deltaP), 1250, 10)
gr.drawString ("deltah", 1200, 50)
gr.drawString ((g_deltaH), 1250, 50)
gr.drawString ("deltab", 1200, 100)
gr.drawString ((g_deltaB), 1250, 100)
gr.drawString ("len p", 1400, 10)
gr.drawString (g_lenP, 1450, 10)
gr.drawString ("len h", 1400, 50)
gr.drawString (g_lenH, 1450, 50)
gr.drawString ("len b", 1400, 100)
gr.drawString (g_lenB, 1450, 100)
gr.drawString ("orient.p", 1600, 10)
gr.drawString (g_pp, 1650, 10)
--gr.drawString ("orient.h", 1600, 50)
--gr.drawString (g_orientNow.h, 1650, 50)
--gr.drawString ("orient.b", 1600, 100)
--gr.drawString (g_orientNow.b, 1650, 100)
gr.drawString (debugtest, 100, 100)
end
]
$State: GS_STATE_GAME_PLAY
$On Frame:
[
DebugSpew ()
if (#shipAnimationList ~= #mn.Ships) then
UpdateShipList ()
end
if (doOnce == false) then
doOnce = true
g_lastFrameTime = mn.getMissionTime ()
end
ProcessContinuousAnimationForShips ()
-- Make sure animationQueue exists and isn't empty (no sense in processing nothing)
if (animationQueue == nil) then
return false
end
if (#animationQueue == 0) then
return
end
for loop = 1, #animationQueue do
local anim = animationQueue [loop]
-- Make sure the subsystem still exists
if not (anim.subsystem:isValid ()) then
table.remove (animationQueue, loop)
break
end
if (mn.getMissionTime () < anim.endTime) then
ProcessAnimation (anim)
end
if (mn.getMissionTime () > anim.endTime) then
-- Animation must be done by now.. deleting it
FinishedSubsystemAnimation (anim)
table.remove (animationQueue, loop)
break
end
end
g_lastFrameTime = mn.getMissionTime ()
]
#End
-
We need more people giving him conflicting suggestions!
subsystem.Orientation is relative to the ship, right? Maybe try this:
I'm not exactly sure, the axis seems to be the same as the ships, but (0,0,0) seems to be relative to the subsystem :confused:
If the program gave coords from 0 to 360 then everything would be fine and dandy.
Edit: question, would it matter if you converted them to quad's? The "damage" has already been done, quads would just preserve the damaged state.?
-
I'm assuming you want a setup where you can tell it "subsystem X go to orientation Y" and the result will look the same regardless of how the ship is oriented, e.g. a door will be in the "open" position. I.e. the subsystem orientation would be specified relative to the ship.
Idk what axis you mean when you say "the axis seems to be the same as the ships"?
This "damage" you're talking about is the weird stuff you're having to deal with using FS2's euler angles, right? And deltas between them? If you do your stuff in quaternions, and only convert to/from euler angles for your interaction with subsystem.Orientation, interpolating between quaternions is linear.
-
I'm assuming you want a setup where you can tell it "subsystem X go to orientation Y" and the result will look the same regardless of how the ship is oriented, e.g. a door will be in the "open" position. I.e. the subsystem orientation would be specified relative to the ship.
Idk what axis you mean when you say "the axis seems to be the same as the ships"?
This part confuses me. Let's say I have a subsystem that's been rotated on the x-axis (port<->star), so that the Z axis (top-down) gets changed locally. Now in-game I want to rotate it by the Z-axis, you would think it would rotate on the local's z-axis, but instead it rotates on the ships z-axis. To compound this, the subsystems rotation angle (0,0,0) is correct. The two just don't make any sense. Here's an example i think, let's say I have a subsystem like a propeller on a helicopter. The pof shows it's perfectly horizontal. Now let's say it's been rotated by 45 degrees so it's facing forward-up. Now to get the blade to spin, we simple rotate on the Z axis. Except it won't work, it'll spin on the subsystems origin, except it won't spin on the blades Z axis, it'll spin on the helicopters Z axis, so it wobble big time. In Max terms, I'm rotating on the world/scene's axis rather than the local axis. Of course there's always the chance I've done something hideously stupid.
This "damage" you're talking about is the weird stuff you're having to deal with using FS2's euler angles, right? And deltas between them? If you do your stuff in quaternions, and only convert to/from euler angles for your interaction with subsystem.Orientation, interpolating between quaternions is linear.
Ya the "damage" is coming from fs2, if it offered quat's then there would be less of a problem, I think. mn.Ships[value].Orientation contains a p,b, & h value of the ships orientation (not 0-360 though). The Delta's are correct in as much as they can. Considering the source is problematic, how do you know a change from 88 degrees to 89 degrees is just 1 or 4 degrees (88->89 or 88->89->90->89)? The delta's are a simple change from the previous ship's rotation to the current rotation (frame by frame)
BTW this is for thrust vectoring where it continually updates the ship's orientation delta and passes it on to the appropriate subsystems. The landing gears I've got working with some hacks.
-
Well that was a good idea, but doesn't work. Looking to see if previousOrientation is getting near 89, but since we don't get a continuous angle, rather jumps based on framerate we can go from something like 72 to something over 90, that won't work. Back to the drawing board.
Another lulz moment.
newOrientation.b = math.rad(180.12341248012423)
local test1 = math.floor (newOrientation.b)
test1 = math.deg (test1)
test1 = 171.1111192312324
I know float points can be off... but this is a bit over the top.
-
Another lulz moment.
newOrientation.b = math.rad(180.12341248012423)
local test1 = math.floor (newOrientation.b)
test1 = math.deg (test1)
test1 = 171.1111192312324
I know float points can be off... but this is a bit over the top.
Not surprising. 180 degrees in radians is 3.14 (= pi), so that is the value of newOrientation.b. You floor that, 3 radians remain for test1. Convert that back to degrees and guess what... 171.9 deg.
Alright, the difference between your 171.11 deg and my 171.9 deg is certainly larger than one would expect, but then again - it's 0.8 deg, I doubt anyone will notice in-game.
-
should also point out that eulers make calculations very hard. thats why you normally convert them to matrices (or quaternions) when you use them. same goes for radians, its what all the math functions take, so just start thinking in fractions of pi.
-
Ya I pretty much have no clue how to solve this one. I don't see an easy way to determine if we're 80->90->80 (100)->70 (120) or if we're really going from 80->90 turn around and go from 90 -> 80->70. Looking at the other two angles (B & H) won't help because they'll be completely different as you move your ship around.
-
i want to say the 3 vertical columns represent the rvec, uvec, and fvec (all 3 would be normalized).
[ rvecx, uvecx, fvecx ]
[ rvecy, uvecy, fvecy ]
[ rvecz, uvecz, fvecz ]
if you do atan2 on the y and z components of the fvec, you can figure out pitch. i would also check whether or not the y axis of the uvec is positive or negative, which would tell you if the ship is upside down or not and if it is flip the y.
function getPitch(m)
local fvecY = m[8]
local fvecZ = m[9]
local uvecY = m[5]
if uvecY >= 0 then
return math.atan2(fvecY,fvecZ )
else
return math.atan2(-fvecY,fvecZ )
end
end
that might not work but idk. you can do the same for yaw if you use rvec x instead of uvec y and fvec x instead of fvec y.
-
Now this looks promising! Only thing I noticed odd was it flipped signs when at 180, but I think I can handle that much better.
Edit: got the sign flip that occurs at 90 degrees. Now just gotta fix the 180 degree issue (...185,186,187,188,189,190,189,188,187...)
-
This handles 90 degree sign flip, temp being the result of the above formula. Sign just returning 1 or -1
if (Sign (previousOrientation) ~= Sign (temp)) then
if (math.abs(previousOrientation) >= math.rad (75)) then
temp = -temp
end
end
So now instead of 0,10,20,30.....80, 90, -100, -110.....
We can go from 0,10,20....160, 170, 179, 179.9, 179, 170, 170,160...0
Hmm do you know how to tell which side of 180 we're on? The Heading and Banking values don't so any sudden massive change. Let me look at the other matrix entries to see if theres any massive changes (sign flippage..etc) going on
Edit: I think I answered it... orientation[6] flips sign when you pass the 180 mark, so now it goes from 360 to 0 to 360... getting closer to the goal.
-
you can check the y axis of the fvec i think. just check if its positive or not, that should tell you which side of 180 its on.
-
Ya, decided to post it after writing all that.
One little jump glitch I gotta figure out why and a more annoying jittering effect as you move. Think I need to do some averaging to create some smoothing results.
-
that may be because the time length of a frame is not constant, just multiply animation stuffs by ba.getFrametime().
-
getFrametime returns how long it thinks it'll take to render the next frame correct?
So I assume it takes X time to process one frame and then multiple X by the value of getFrameTime? Right now I'm capture the change in pitch at every frame and comparing it to the previous pitch from the previous frame.
local value = CalcCorrectPitch (animationShipData)
local deltaP = value - animationShipData.PreviousPitch
Is there a more precise version of getMissionTime thats in milliseconds rather than seconds?
-
isn't mission time a float? in that case to get milisecond, multiply by 1000 :D.
-
oh duh... I was already doing something like that lol
I think part of the problem is there's a constant change in the pitch deltas. I'm going to try and round it to 5 degree increments and see if that helps.
-
Hmmm, still having issues
Here are the delta angles in radians over a short period of time. I'm using the keyboard arrow keys to move around so the ships rotation should be relatively consistent.
(http://img.photobucket.com/albums/v356/Shodan_AI/numbernoise_zps08dfdcbd.jpg)
as you can see they seem to jump around quite a bit. There's got to be a way to remove all the "noise"
-
is this delta per frame or delta per unit time?
-
is this delta per frame or delta per unit time?
Per frame. frame rate is generally about 60fps.
Here's the rounding code i made
local rounding = round (math.rad (25),2)
local radtest = (update*100)
radtest = radtest / rounding
radtest = math.floor (radtest)
radtest = radtest * rounding
radtest = radtest / 100
It's suppose to round down every 25 degrees... don't think it's actually working through.
Edit: I take that back, i switched
if ((g_lastFrameTime+5) > GetMissionMilliseconds ()) then
return false
else
g_lastFrameTime = GetMissionMilliseconds () + 5
end
This runs before doing the continuous animation sequences (vectoring stuff)
Also there seems to be a bug around the i think is the 90 or 180 degree mark, the delta changes signs for one sequence then returns to normal, I'm guessing this is probably something in my CalcCorrectPitch function or similar, it shouldn't be that hard to track down i hope. The slight jittering is more problematic right now.
-
This whole section has devolved into an ungodly mess, time for a restart.
- Check to see if current mission time is or exceeds X time mn.GetMissionTime () > nextTimeFrame + .1
- Grab the ships current P,B & H values
- Convert these (at least P) to the 0->360 values (code you gave me plus some of mine do this
- Create the Delta P by subtracting the current P with the previous P (adjust for the 360 to 0 in case)
- Go through all the subsystems, if one has a thrust vector flag do this:
- Multiple the deltaP by some factor so the animation will be visible, apply limits if needed, same with H & B
- Assign this delta P/H/B to the subsystem's pitch (since all subsystems are (0,0,0) based that should be the pitch for it)
- Store the ships orientation as the previous version
-
Great... I had the pitch rotation degrees working perfectly... 0->360 and 0->-360
But guess what! Banking throws everything for a loop :ick:
Hmmm I'm going to try and reset the bank/heading back to 0 and then calc the pitch.
-
Ugh.. this is insane. I was just looking at the banking angle. Say I bank to some degree (can be even minor) then pitch the ship. Guess what the banking angle changes! :banghead:
Is this one of the reasons why eulers are so disliked? Also is there any other way to obtain the ships orientation?
Edit: I'm going to skip thrust vectoring for the moment and thrust acceleration/deaccleration animations :)
-
OMG we were doing this the hard way! Go look at the scripting.html Now look at the ship "class". It's an OBJECT object, now go look at the object "class". There's a "physics" member. And in the physics class guess what! "Rotational Velocity" Value: Rotational velocity, or null vector if handle is invalid
A quick test show's it seems to be accurate. Doing a full 360 pitch show's no major change in delta values. Looks like we might actually have thrust vectoring soon! :D
-
oh yea, i forgot about that. :D
-
LULZ... it only took less than a day and some small code to get thrust vectoring to actual work. :lol:
Now back on to thrust acceleration/deacc. (think air brakes, thrusters that can open up or clamp down depending on speed.
-
Figured out how to do "classes" so time for some cleanup. Sorry nuke your example was a bit too complicated :D
-
its really not complete either. there is no set in stone way to do object oriented programming in lua, there are a hodgepodge of methods and im only really familiar with a few of them.
-
Here's how I'm doing them:
ThrustVectorType = {
vectorFactor = nil,
minAngle = nil,
maxAngle = nil,
otherAxisEffects = nil
}
ThrustVectorType.__index = ThrustVectorType
function ThrustVectorType:Create ()
local newEntry = {}
setmetatable (newEntry, ThrustVectorType)
return newEntry
end
Then use setters and getters for the members.
Doing some parameter check coding now and converting to class, then I'll continue on to thrust velocity checking.
-
Thrust vectoring in action!
https://www.youtube.com/watch?v=GqiCbwL13-Y&feature=youtu.be (https://www.youtube.com/watch?v=GqiCbwL13-Y&feature=youtu.be)
-
pretty cool. have you had a chance to test what that does to collision detection?
-
pretty cool. have you had a chance to test what that does to collision detection?
No clue. If there's a problem I think the solution would have to be in the engine. Hmm I don't suppose you can activate/view collision boxes in-game?
Also nuke since you probably have the scp source code there, can you check to see if Subsystem.Position is read only? I can read it but I can't seem to modify it. Since I'm on a roll here I want to test future ideas.
-
as i understand it if you enlarge the bbox and radius of the parent to contain all possible rotated positions of the child, then collision detection should be preserved. but ive never tested this idea at all with scripted rotations.
-
Also nuke since you probably have the scp source code there, can you check to see if Subsystem.Position is read only? I can read it but I can't seem to modify it. Since I'm on a roll here I want to test future ideas.
Generally any virtvar like "object.value" can be set to a new value (as opposed to functions like "object:function()" that may do different things). What the game does with this input is another story. If it decides to write something else into that variable (for example because your input is not within the accepted range) before using it the next time you are out of luck.
-
as i understand it if you enlarge the bbox and radius of the parent to contain all possible rotated positions of the child, then collision detection should be preserved. but ive never tested this idea at all with scripted rotations.
The old tiny triangle trick. I did that with some of the kilrathi capships for blown-off subsystems.
Also nuke since you probably have the scp source code there, can you check to see if Subsystem.Position is read only? I can read it but I can't seem to modify it. Since I'm on a roll here I want to test future ideas.
Generally any virtvar like "object.value" can be set to a new value (as opposed to functions like "object:function()" that may do different things). What the game does with this input is another story. If it decides to write something else into that variable (for example because your input is not within the accepted range) before using it the next time you are out of luck.
Ah ok.. i'll have to do some more inspecting... would be very cool if I could make it so we can do translations too instead of faking it with super wide angles.
Also good news, a subsystem's children will also be effected by the rotation. I was kinda concerned there for a moment.
-
now heres the thing with doing long animation hierarchy chains. you have to have containment all down the chain, each parent's bbox must completely contain everything that is a child of it all the time. if the animation causes something to rotate out of its parent's bbox (or its own), it may be missed by collision detection. fortunately instance depth is only like 5, so you can only have hierarchy that deep, including the hull model. i havent attempted anything that complex, so this is all just in theory.
i mean ive done this kinda thing:
(http://i213.photobucket.com/albums/cc103/Emperor_of_Nihil/door.gif)
and had it work in game, but the collision detection never worked, and i dont think i understood it enough at the time to attempt boxing it to hell and back.
-
Also this is more meant for small stuff where collision detection wouldn't be that big of a deal. If you want to move a massive chunk of a 5km ship around, you'll probably want a better solution.
Also this increases your subsystem count up quite a bit, depending how detailed you get.
-
Thruster nozzle resizing now works. Basically roughly source orientation->dest orientation based on (speed/maxspeed) {its more tweakable than that but that's the jist}
-
That sounds really nice. Are you going to release a public beta?
-
Ya, and i'll include a demo ship along with it. Just want to get it as full featured as possible.
Unfortunately it looks like translation doesn't work. Seems like the engine only gives you the position and never reads from it (basically you can read it but never modify it)
-
Here's an interesting bug:
function ShipType:GetSpecificSubsystem (name)
assert (name ~= nil, "ShipType:GetSpecificSubsystem (name) name is nil")
local result = nil
for loop = 1, #self.subsystemList do
local subsystem = self.subsystemList[loop]
if (subsystem:GetName () == name) then
table.insert (g_orientNow, subsystem:GetName ())
table.insert (g_orientNow, "I'M HERE!!!")
result = subsystem
end
end
--table.insert (g_orientNow, "this->"..result:GetName ())
return result
end
self.subsystemList contains a list of subsystems objects (they have a GetName () function).
The loop goes through each of the entries looking for the correct name.
It finds the correct one ("I'm HERE!!!" gets displayed on the screen so I know it's found it)
Guess what! result is NIL :confused:
Even if I replace result = subsystem with return subsystem it's still a nil. Now I know subsystem can't be a nil, because GetName () would have crashed with nil
-
it basically wants a string but got nil for some reason instead. the function 'GetSpecificSubsystem' was simply given a non-string value. just do a type(name) == "string" check at the top of the function to make sure you bail if the input is bad.
another thing to watch out for is that if under any circumstances GetName() returned nil and name also happened to be nil, then the condition would pass and return nil as the result. *
another thing i noticed, since the result defaults to nil, if it goes through the loop and doesnt find a subsystem that matches the name, it will return nil by default.
*cant be nil because assert
-
Thing is.. it's finding it! The two table.inserts are actually displaying. Not only that it's showing the correct subsystem name that i asked for. Weird. :wtf:
Think I found it... problem was higher up the chain. I was looking for two entries (forgot about the second one), it found the first but couldn't find the second
-
Another piece of good news... a subsystem's subsystem will automatically include any rotation from it's parent. Although I am having a couple problems with thruster glows, first it orientates itself in the opposite direction it's suppose to go, and second it doesn't move to stay in the same position on the subsystem. It could be I'm just not attaching them correctly.
-
Is it possible to do something like this in lua?
void doStuff ()
{
}
.
.
.
myData.FunctionName = doStuff
.
.
.
.
myData.FunctionName ()
it won't let me do execute FunctionName(). What i've found out on google about callbacks is mostly connecting it with C/C++
-
Uh... you didn't actually declare it as void doStuff(), did you?
myData.FunctionName = function() --[[ implementation go here ]]-- end
...or if you don't want to do it inline
doStuff = function()
-- implementation go here
end
myData.FunctionName = doStuff
myData.FunctionName()
Should work too.
Or is myData a userdata? If it's a userdata you can't add custom members like you can with normal lua objects (i.e. tables).
-
Sorry I forgot an important sentence... is it possible to do it in Lua?
edit: Basically this is what I'm aiming for:
What I want to have the model maker write (each ship class get's it's own set of functions. aka... RegisterBearcat, RegisterBlackWidow, RegisterHellcat....etc)
function ShipType:MyShipClassLandGear (isOpening)
if (isOpening)
-- do opening stuff, choose which subsystem group to activate (for fancy stuff)
else
-- do closing stuff, choose which subsystem group to activate (for fancy stuff)
end
end
and only have to write this too (same as above, each ship class gets their own):
function ShipType:RegisterMyShipClass (stuff.....)
-- Do subsystem animation registeration (define what is landing gear, thrust vectoring, thrust nozzles...etc)
self:LandingGearFunction = self.MyShipClassLandingGear
end
then just call OpenLandingGear (ship name) via FRED
then the prewritten code (stuff i'm doing now) will automatically handle it via:
function OpenLandingGear (name)
-- Grab the necessary information (ship, triggered group [landing gear], etc....)
-- theShip is of type ShipType
theShip:OpenLandingGear (isOpening)
end
That way you don't have to write a massive if-then-else for each ship class.
This whole thing is rather complicated using triggers and ship classes. but it'll allow for not only each type of ship to have it's own information (in fact each ship has it's own data), but more importantly it allows for chaining animations together to make more advanced effects than what the built-in rotation animation can do.
-
a little word of advise: never tell a modder to write script. :lol:
even it ifs something stupid like a list of variables. seriously, they will give you so much flak.
-
LOL well until I can read subsystem properties, it'll have to do :p
Also it's mostly just copy and paste.
Also found the answer to my question, buried on googles pages.
function ShipType:BearcatLandingGear (opening)
return function (opening)
-- do stuff here
table.insert (g_animationDebug, "got here!")
end
end
-
OMG this was sooo frustrating...
DO NOT DO THIS!
SubsystemListType = {
groups = {}
}
SubsystemListType.__index = SubsystemListType
function SubsystemListType:Create ()
local newEntry = {}
setmetatable (newEntry, SubsystemListType)
return newEntry
end
DO THIS INSTEAD!
SubsystemListType = {
groups = nil
}
SubsystemListType.__index = SubsystemListType
function SubsystemListType:Create ()
local newEntry = {}
setmetatable (newEntry, SubsystemListType)
newEntry.groups = {}
return newEntry
end
Otherwise EVERY object you create from SubsystemList will have exactly the same "groups".
I.e.
item1 = SubsystemList.Create ()
table.insert (item1.groups, 1)
table.insert (item1.groups, 2)
table.insert (item2.groups, 666)
both
#item1.groups
and
#item2.groups
will give you 3!
Each list must be initialized in it's own self.class
-
Code cleanup continues... throwing in asserts for validation checking. Rewrote a ton of stuff for better usage of groups. Still some things I want to do about them... right now it's a simple chained (one group can activate another group which in turn can activate.....) to where a group can activate a list of groups. Think not only chained, but branched out animations.
Also I just realized how powerful this could be.... the simple landing gear type will be able to do nuke's
(http://i213.photobucket.com/albums/cc103/Emperor_of_Nihil/door.gif)
without much difficulty. The hardest part it guessing the right angles and speeds.
-
first rotation i believe is positive 90 degrees. the second is negative 180 and im not sure about the 3rd, i think 90.
-
Anyone that was watching this thread, I've posted an alpha version in this thread: http://www.hard-light.net/forums/index.php?topic=83947.0