├── LICENSE ├── README.md ├── boneanimlib.lua ├── cl_animeditor.lua ├── cl_boneanimlib.lua ├── mocap_controller.lua ├── sh_boneanimlib.lua ├── sh_mocap.lua └── sv_mocap.lua /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | animationsapi 2 | ============= 3 | 4 | Garry's Mod API which allows the creation and usage of custom model animations stored as Lua tables rather than animations in the model file. 5 | -------------------------------------------------------------------------------- /boneanimlib.lua: -------------------------------------------------------------------------------- 1 | if CLIENT or GetLuaAnimations ~= nil then return end 2 | 3 | include("sh_boneanimlib.lua") 4 | include("sh_mocap.lua") 5 | AddCSLuaFile("cl_boneanimlib.lua") 6 | AddCSLuaFile("sh_boneanimlib.lua") 7 | AddCSLuaFile("cl_animeditor.lua") 8 | AddCSLuaFile("sh_mocap.lua") 9 | AddCSLuaFile("mocap_controller.lua") 10 | 11 | hook.Add("Initialize", "BAL_Initialize", function() 12 | util.AddNetworkString("bal_reset") 13 | util.AddNetworkString("bal_set") 14 | util.AddNetworkString("bal_stop") 15 | util.AddNetworkString("bal_stopgroup") 16 | util.AddNetworkString("bal_stopall") 17 | end) 18 | 19 | -- These are unreliable. All Lua animations should be set on both the client and server (predicted). 20 | local meta = FindMetaTable("Entity") 21 | if not meta then return end 22 | 23 | function meta:ResetLuaAnimation(sAnimation, fTime, fPower, fTimeScale) 24 | net.Start("bal_reset") 25 | net.WriteEntity(self) 26 | net.WriteString(sAnimation) 27 | net.WriteFloat(fTime or -1) 28 | net.WriteFloat(fPower or -1) 29 | net.WriteFloat(fTimeScale or -1) 30 | net.Broadcast() 31 | end 32 | 33 | function meta:SetLuaAnimation(sAnimation, fTime, fPower, fTimeScale) 34 | net.Start("bal_set") 35 | net.WriteEntity(self) 36 | net.WriteString(sAnimation) 37 | net.WriteFloat(fTime or -1) 38 | net.WriteFloat(fPower or -1) 39 | net.WriteFloat(fTimeScale or -1) 40 | net.Broadcast() 41 | end 42 | 43 | function meta:StopLuaAnimation(sAnimation, fTime) 44 | net.Start("bal_stop") 45 | net.WriteEntity(self) 46 | net.WriteString(sAnimation) 47 | net.WriteFloat(fTime or 0) 48 | net.Broadcast() 49 | end 50 | 51 | function meta:StopLuaAnimationGroup(sAnimation, fTime) 52 | net.Start("bal_stopgroup") 53 | net.WriteEntity(self) 54 | net.WriteString(sAnimation) 55 | net.WriteFloat(fTime or 0) 56 | net.Broadcast() 57 | end 58 | 59 | function meta:StopAllLuaAnimations(fTime) 60 | net.Start("bal_stopall") 61 | net.WriteEntity(self) 62 | net.WriteFloat(fTime or 0) 63 | net.Broadcast() 64 | end 65 | -------------------------------------------------------------------------------- /cl_animeditor.lua: -------------------------------------------------------------------------------- 1 | if _INCLUDEDANIMEDITOR then return end 2 | _INCLUDEDANIMEDITOR = true 3 | 4 | surface.CreateFont("DefaultFontVerySmall", {font = "tahoma", size = 10, weight = 0, antialias = false}) 5 | surface.CreateFont("DefaultFontSmall", {font = "tahoma", size = 11, weight = 0, antialias = false}) 6 | surface.CreateFont("DefaultFontSmallDropShadow", {font = "tahoma", size = 11, weight = 0, shadow = true, antialias = false}) 7 | surface.CreateFont("DefaultFont", {font = "tahoma", size = 13, weight = 500, antialias = false}) 8 | surface.CreateFont("DefaultFontBold", {font = "tahoma", size = 13, weight = 1000, antialias = false}) 9 | surface.CreateFont("DefaultFontLarge", {font = "tahoma", size = 16, weight = 0, antialias = false}) 10 | 11 | local boneList = {} 12 | 13 | boneList["ValveBiped.Bip01"] = { 14 | "ValveBiped.Bip01_Pelvis", 15 | "ValveBiped.Bip01_Spine", 16 | "ValveBiped.Bip01_Spine1", 17 | "ValveBiped.Bip01_Spine2", 18 | "ValveBiped.Bip01_Spine4", 19 | "ValveBiped.Bip01_Neck1", 20 | "ValveBiped.Bip01_Head1", 21 | "ValveBiped.Bip01_R_Clavicle", 22 | "ValveBiped.Bip01_R_UpperArm", 23 | "ValveBiped.Bip01_R_Forearm", 24 | "ValveBiped.Bip01_R_Hand", 25 | "ValveBiped.Bip01_L_Clavicle", 26 | "ValveBiped.Bip01_L_UpperArm", 27 | "ValveBiped.Bip01_L_Forearm", 28 | "ValveBiped.Bip01_L_Hand", 29 | "ValveBiped.Bip01_R_Thigh", 30 | "ValveBiped.Bip01_R_Calf", 31 | "ValveBiped.Bip01_R_Foot", 32 | "ValveBiped.Bip01_R_Toe0", 33 | "ValveBiped.Bip01_L_Thigh", 34 | "ValveBiped.Bip01_L_Calf", 35 | "ValveBiped.Bip01_L_Foot", 36 | "ValveBiped.Bip01_L_Toe0" 37 | } 38 | boneList["ValveBiped"] = { 39 | "ValveBiped.hips", 40 | "ValveBiped.leg_bone1_L", 41 | "ValveBiped.leg_bone2_L", 42 | "ValveBiped.leg_bone3_L", 43 | "ValveBiped.Bip01_L_Foot", 44 | "ValveBiped.Bip01_L_Toe0", 45 | "ValveBiped.leg_bone1_R", 46 | "ValveBiped.leg_bone2_R", 47 | "ValveBiped.leg_bone3_R", 48 | "ValveBiped.Bip01_R_Foot", 49 | "ValveBiped.Bip01_R_Toe0", 50 | "ValveBiped.spine1", 51 | "ValveBiped.spine2", 52 | "ValveBiped.spine3", 53 | "ValveBiped.spine4", 54 | "ValveBiped.neck1", 55 | "ValveBiped.neck2", 56 | "ValveBiped.head", 57 | "ValveBiped.clavical_L", 58 | "ValveBiped.arm1_L", 59 | "ValveBiped.arm2_L", 60 | "ValveBiped.hand1_L", 61 | "ValveBiped.clavical_R", 62 | "ValveBiped.arm1_R", 63 | "ValveBiped.arm2_R", 64 | "ValveBiped.hand1_R", 65 | "ValveBiped.bone", 66 | "ValveBiped.bone1", 67 | "ValveBiped.bone2" 68 | 69 | } 70 | 71 | local animationData = {} 72 | 73 | local function animprint() 74 | PrintTable(animationData) 75 | end 76 | concommand.Add("animprint",animprint) 77 | 78 | 79 | local animName 80 | local animType 81 | local subAnimationsLoaded = {} 82 | local playBarOffset = 0 83 | local playingAnimation = false 84 | local camDist = 200 85 | local camHeight = 60 --offset from GetPos 86 | local pressedPos = {} 87 | local angToPlayer = Angle(0,0,0) 88 | local selectedBoneSet = "ValveBiped.Bip01" 89 | local selectedFrame 90 | local draggingDir 91 | local tblLineEndPoints = {} 92 | local timeLine 93 | local mainSettings 94 | local sliders 95 | local subAnims 96 | local animating = false 97 | local editingAnimation = false 98 | local rightDown = false 99 | local leftDown = false 100 | local mwheelDown = false 101 | local TypeTable = {} 102 | TypeTable[0] = "TYPE_GESTURE" 103 | TypeTable[1] = "TYPE_POSTURE" 104 | TypeTable[2] = "TYPE_STANCE" 105 | TypeTable[3] = "TYPE_SEQUENCE" 106 | 107 | local function DistFromPointToLine(x,y,x1,y1,x2,y2) 108 | 109 | local A = x - x1; 110 | local B = y - y1; 111 | local C = x2 - x1; 112 | local D = y2 - y1; 113 | 114 | local dot = A * C + B * D; 115 | local len_sq = C * C + D * D; 116 | local param = dot / len_sq; 117 | 118 | local xx,yy; 119 | 120 | if(param < 0) then 121 | 122 | xx = x1; 123 | yy = y1; 124 | elseif(param > 1) then 125 | 126 | xx = x2; 127 | yy = y2; 128 | 129 | else 130 | xx = x1 + param * C; 131 | yy = y1 + param * D; 132 | end 133 | return math.Dist(x,y,xx,yy) 134 | 135 | 136 | 137 | end 138 | 139 | local function PaintTopBar() 140 | local wide = ScrW() 141 | draw.RoundedBox(0,0,0,wide,26,Color(0,0,0,255)) 142 | draw.RoundedBox(0,1,1,wide-2,24,Color(50,50,50,255)) 143 | draw.SimpleText("Lua Animation Editor (API by JetBoom)","DefaultFont",25,13,Color(255,255,255,255),0,1) 144 | if type(animName ) == "string" then 145 | draw.SimpleText("Working On: ("..animName..")","DefaultFont",wide*0.5,13,Color(255,255,255,255),1,1) 146 | else 147 | draw.SimpleText("No Animation Loaded!","DefaultFont",wide*0.5,13,Color(255,255,255,255),1,1) 148 | end 149 | 150 | 151 | if selectedBone && selectedBone != "" then 152 | 153 | local boneID = LocalPlayer():LookupBone(selectedBone) 154 | 155 | if boneID then 156 | local matrix = LocalPlayer():GetBoneMatrix(boneID) 157 | local vec = matrix:GetTranslation() 158 | local ang = matrix:GetAngles() 159 | 160 | local upDir = ang:Up()*20 161 | local rightDir = ang:Right()*20 162 | local forwardDir = ang:Forward()*20 163 | 164 | local origin = vec:ToScreen() 165 | 166 | if !leftDown && draggingDir then 167 | draggingDir = nil 168 | end 169 | 170 | surface.SetDrawColor(255,0,0,255) 171 | local p1 = (vec+rightDir):ToScreen() 172 | 173 | local dist = DistFromPointToLine(gui.MouseX(),gui.MouseY(),origin.x,origin.y,p1.x,p1.y) 174 | if (dist < 10 && !draggingDir) || draggingDir == "RR" then 175 | surface.SetDrawColor(255,255,255,255) 176 | draggingDir = "RR" 177 | tblLineEndPoints[1] = {x=origin.x,origin.y} 178 | tblLineEndPoints[1] = {p1.x,p1.y} 179 | end 180 | 181 | 182 | 183 | surface.DrawLine(origin.x,origin.y,p1.x,p1.y) 184 | 185 | surface.SetDrawColor(0,255,0,255) 186 | local p2 = (vec+upDir):ToScreen() 187 | 188 | local dist = DistFromPointToLine(gui.MouseX(),gui.MouseY(),origin.x,origin.y,p2.x,p2.y) 189 | if (dist < 10 && !draggingDir) || draggingDir == "RU" then 190 | surface.SetDrawColor(255,255,255,255) 191 | draggingDir = "RU" 192 | tblLineEndPoints[1] = {x=origin.x,origin.y} 193 | tblLineEndPoints[1] = {p1.x,p1.y} 194 | end 195 | 196 | surface.DrawLine(origin.x,origin.y,p2.x,p2.y) 197 | 198 | surface.SetDrawColor(0,0,255,255) 199 | local p3 = (vec+forwardDir):ToScreen() 200 | 201 | local dist = DistFromPointToLine(gui.MouseX(),gui.MouseY(),origin.x,origin.y,p3.x,p3.y) 202 | if (dist < 10 && !draggingDir) || draggingDir == "RF" then 203 | surface.SetDrawColor(255,255,255,255) 204 | draggingDir = "RF" 205 | tblLineEndPoints[1] = {x=origin.x,origin.y} 206 | tblLineEndPoints[1] = {p1.x,p1.y} 207 | end 208 | surface.DrawLine(origin.x,origin.y,p3.x,p3.y) 209 | 210 | 211 | end 212 | 213 | 214 | end 215 | 216 | end 217 | 218 | 219 | 220 | 221 | 222 | local animEditorPanels = {} 223 | 224 | local function AnimationStarted(bLoaded) 225 | subAnims:Refresh() 226 | if !bLoaded then 227 | animationData = {} 228 | animationData.FrameData = {} 229 | animationData.Type = animType 230 | 231 | for i,v in pairs(animEditorPanels) do 232 | if v.OnNewAnimation then 233 | v:OnNewAnimation() 234 | end 235 | end 236 | else 237 | 238 | for i,v in pairs(animEditorPanels) do 239 | if v.OnLoadAnimation then 240 | v:OnLoadAnimation() 241 | end 242 | end 243 | end 244 | editingAnimation = true 245 | timeLine.subAnims:Clear() 246 | table.Empty(subAnimationsLoaded) 247 | 248 | end 249 | 250 | 251 | local function NewAnimation() 252 | local frame = vgui.Create("DFrame") 253 | 254 | form = vgui.Create("DForm",frame) 255 | form:SetPos(5,25) 256 | form:SetWide(300) 257 | form:SetTall(300) 258 | form:SetName("Animation Properties") 259 | local entry = form:TextEntry("Animation Name") 260 | 261 | local info = form:Help([[Gestures are keyframed animations that use the current position and angles of the bones. They play once and then stop automatically. 262 | 263 | Postures are static animations that use the current position and angles of the bones. They stay that way until manually stopped. Use TimeToArrive if you want to have a posture lerp. 264 | 265 | Stances are keyframed animations that use the current position and angles of the bones. They play forever until manually stopped. Use RestartFrame to specify a frame to go to if the animation ends (instead of frame 1). 266 | 267 | Sequences are keyframed animations that use the origin and angles of the entity. They play forever until manually stopped. Use RestartFrame to specify a frame to go to if the animation ends (instead of frame 1). 268 | You can also use StartFrame to specify a starting frame for the first loop.]]) 269 | local type = form:ComboBox("Animation Type") 270 | type:SetTall(100) 271 | 272 | type:AddChoice("TYPE_GESTURE", TYPE_GESTURE) 273 | type:AddChoice("TYPE_POSTURE", TYPE_POSTURE) 274 | type:AddChoice("TYPE_STANCE", TYPE_STANCE) 275 | type:AddChoice("TYPE_SEQUENCE", TYPE_SEQUENCE, true) 276 | local help = form:Help("Select your options") 277 | local begin = form:Button("Begin") 278 | begin.DoClick = function() 279 | animName = entry:GetValue() 280 | animType = _G[type:GetText()] 281 | 282 | if animName == "" then help:SetText("Write a name for this animation") return end 283 | if !animType then help:SetText("Select a valid animation type!") return end 284 | frame:Remove() 285 | AnimationStarted() 286 | end 287 | frame:MakePopup() 288 | 289 | timer.Simple(0.01,function()frame:SetSize(form:GetWide()+10,450) frame:Center() end) 290 | end 291 | 292 | local function LoadAnimation() 293 | 294 | local frame = vgui.Create("DFrame") 295 | frame:SetSize(300,300) 296 | frame:SetTitle("Load Animation") 297 | local box = vgui.Create("DComboBox",frame) 298 | --box:SetMultiple(false) adurp 299 | box:StretchToParent(5,25,5,35) 300 | 301 | for i,v in pairs(GetLuaAnimations()) do 302 | if i != "editortest" && i != "editingAnim" && !string.find(i,"subPosture_") then --anim editor uses this internally 303 | box:AddChoice(i) 304 | end 305 | end 306 | 307 | local button = vgui.Create("DButton",frame) 308 | button:SetWide(frame:GetWide()-10) 309 | button:SetPos(5,frame:GetTall()-25) 310 | button:SetText("Load Animation") 311 | button.DoClick = function() 312 | 313 | animName = box:GetText() 314 | animationData = GetLuaAnimations()[animName] 315 | animType = animationData.Type 316 | frame:Remove() 317 | AnimationStarted(true) 318 | LocalPlayer():StopAllLuaAnimations() 319 | end 320 | 321 | frame:Center() 322 | end 323 | 324 | --return lua code for the loaded animation 325 | local function OutputCode() 326 | if !animName then surface.PlaySound("buttons/button10.wav") return end 327 | local animData = table.Copy(animationData) 328 | 329 | --clean out unneeded entries 330 | for i,v in pairs(animData.FrameData) do 331 | for BoneName,BoneData in pairs(v.BoneInfo) do 332 | for MoveRot,Val in pairs(BoneData) do 333 | if Val == 0 then 334 | animData.FrameData[i].BoneInfo[BoneName][MoveRot] = nil 335 | end 336 | end 337 | end 338 | end 339 | 340 | 341 | 342 | 343 | local str = "RegisterLuaAnimation('"..animName.."', {\r\n" 344 | str = str .. "\tFrameData = {\r\n" 345 | local numFrames = table.Count(animData.FrameData) 346 | local numFrame = 1 347 | for frameIndex,frameData in pairs(animData.FrameData) do 348 | 349 | local commaFrame = "," 350 | if numFrame == numFrames then commaFrame = "" end 351 | 352 | 353 | 354 | str = str .. "\t\t{\r\n" 355 | str = str .. "\t\t\tBoneInfo = {\r\n" 356 | local numBones = table.Count(frameData.BoneInfo) 357 | local numBone = 1 358 | 359 | for boneName,boneData in pairs(frameData.BoneInfo) do 360 | local commaBone = "," 361 | if numBones == numBone then 362 | commaBone = "" 363 | end 364 | str = str .. "\t\t\t\t['"..boneName.."'] = {\r\n" 365 | local numChanges = table.Count(boneData) 366 | local numInner = 1 367 | 368 | for MoveRot,Value in pairs(boneData) do 369 | 370 | local commaInner = "," 371 | if numChanges == numInner then commaInner = "" end 372 | 373 | local innerStr = "\t\t\t\t\t"..MoveRot.." = "..Value..commaInner.."\r\n" 374 | str = str..innerStr 375 | numInner = numInner + 1 376 | 377 | 378 | 379 | end 380 | 381 | 382 | str = str .. "\t\t\t\t}"..commaBone.."\r\n" 383 | 384 | numBone = numBone + 1 385 | end 386 | 387 | 388 | 389 | numFrame = numFrame + 1 390 | 391 | str = str .. "\t\t\t},\r\n" 392 | if frameData.FrameRate then 393 | str = str .. "\t\t\tFrameRate = "..frameData.FrameRate.."\r\n" 394 | end 395 | 396 | 397 | str = str .. "\t\t}"..commaFrame.."\r\n" 398 | 399 | end 400 | str = str .. "\t},\r\n" 401 | 402 | if animData.RestartFrame then 403 | str = str .. "\tRestartFrame = "..animData.RestartFrame..",\r\n" 404 | end 405 | if animData.StartFrame then 406 | str = str .. "\tStartFrame = "..animData.StartFrame..",\r\n" 407 | end 408 | 409 | str = str .. "\tType = "..TypeTable[animData.Type].."\r\n})" 410 | 411 | 412 | 413 | return str 414 | end 415 | 416 | 417 | --calculates all the bone movements up to the current frame for DISPLAY PURPOSES. 418 | local function ApplyEndResults() 419 | local currentFrame = selectedFrame:GetAnimationIndex() 420 | local postureAnim = {Type = TYPE_POSTURE,FrameData = {{BoneInfo = {}}}} 421 | 422 | --[[local timeInSeconds = 0 423 | for frameIndex,frameData in pairs(animationData.FrameData) do 424 | timeInSeconds = timeInSeconds + 1/(frameData.FrameRate or 1) 425 | 426 | for boneName, boneData in pairs(frameData.BoneInfo) do 427 | postureAnim.FrameData[1].BoneInfo[boneName] = postureAnim.FrameData[1].BoneInfo[boneName] or {} 428 | for moveType,moveVal in pairs(boneData) do 429 | postureAnim.FrameData[1].BoneInfo[boneName][moveType] = (postureAnim.FrameData[1].BoneInfo[boneName][moveType] or 0) + moveVal 430 | end 431 | end 432 | 433 | if frameIndex == currentFrame then break end 434 | end]] 435 | postureAnim.FrameData[1] = table.Copy(animationData.FrameData[currentFrame]) 436 | 437 | 438 | --[[local subPostures = {} 439 | --load all the sub animations up to the exact point where the keyframe in the main animation ends... 440 | for i,v in pairs(subAnimationsLoaded) do 441 | 442 | local subPostureAnim = {Type = TYPE_POSTURE,FrameData = {{BoneInfo = {}}}} 443 | 444 | 445 | local anim = GetLuaAnimations()[i] 446 | local totalAnimTimeInSeconds = 0 447 | local timeToStart = 0 448 | local timeSoFar = 0 449 | 450 | --get the time from the actual start(0) to the animation start (StartFrame) 451 | if anim.StartFrame && anim.StartFrame > 1 then 452 | for i=1,anim.StartFrame-1 do 453 | 454 | timeToStart = timeToStart + 1/(anim.FrameData[i].FrameRate or 1) 455 | end 456 | end 457 | 458 | 459 | --pregather animation time 460 | for i=anim.StartFrame or 1,table.getn(anim.FrameData) do 461 | 462 | totalAnimTimeInSeconds = totalAnimTimeInSeconds + 1/(anim.FrameData[i].FrameRate or 1) 463 | end 464 | 465 | for frameIndex=1,table.getn(anim.FrameData) do 466 | 467 | 468 | local frameData = anim.FrameData[frameIndex] 469 | 470 | 471 | --this frame starts before the selected main keyframe ends 472 | if timeSoFar < timeInSeconds then 473 | 474 | 475 | local prevTime = timeSoFar 476 | timeSoFar = timeSoFar + 1/(frameData.FrameRate or 1) 477 | 478 | 479 | 480 | 481 | 482 | --we've reached a keyframe that extends beyond our main animation's current keyframe endpos 483 | local delta = 1 484 | if timeSoFar > timeInSeconds && (anim.StartFrame or 1) <= frameIndex then 485 | --thanks sassafrass 486 | delta = (timeInSeconds-prevTime)/(timeSoFar-prevTime) 487 | end 488 | 489 | for boneName,boneData in pairs(frameData.BoneInfo) do 490 | subPostureAnim.FrameData[1].BoneInfo[boneName] = subPostureAnim.FrameData[1].BoneInfo[boneName] or {} 491 | for moveType,moveVal in pairs(boneData) do 492 | if !subPostureAnim.FrameData[1].BoneInfo[boneName][moveType] then 493 | subPostureAnim.FrameData[1].BoneInfo[boneName][moveType] = moveVal*delta 494 | 495 | else 496 | subPostureAnim.FrameData[1].BoneInfo[boneName][moveType] = subPostureAnim.FrameData[1].BoneInfo[boneName][moveType] + moveVal*delta 497 | end 498 | end 499 | end 500 | end 501 | end 502 | RegisterLuaAnimation("subPosture_"..i,subPostureAnim) 503 | table.insert(subPostures,"subPosture_"..i) 504 | 505 | end]] 506 | 507 | RegisterLuaAnimation("editingAnim",postureAnim) 508 | 509 | 510 | LocalPlayer():StopAllLuaAnimations() 511 | LocalPlayer():SetLuaAnimation("editingAnim") 512 | --[[for i,v in pairs(subPostures) do 513 | LocalPlayer():SetLuaAnimation(v) 514 | end]] 515 | end 516 | 517 | local function LoadAnimationFromFile() 518 | 519 | local frame = vgui.Create("DFrame") 520 | frame:SetSize(300,300) 521 | frame:SetTitle("Load Animation From File") 522 | local box = vgui.Create("DComboBox",frame) 523 | --box:SetMultiple(false) 524 | box:StretchToParent(5,25,5,35) 525 | for i,v in pairs(file.Find("animations/*.txt", "DATA")) do 526 | box:AddChoice(string.sub(v,1,-5)) 527 | end 528 | 529 | local button = vgui.Create("DButton",frame) 530 | button:SetWide(frame:GetWide()-10) 531 | button:SetPos(5,frame:GetTall()-25) 532 | button:SetText("Load Animation") 533 | button.DoClick = function() 534 | 535 | local name = box:GetText() 536 | 537 | local str = file.Read("animations/"..name..".txt", "DATA") 538 | if !str then return end 539 | local success, t = pcall(util.JSONToTable, str) 540 | if !success then 541 | ErrorNoHalt("WARNING: Animation '"..name.."' failed to load\n") 542 | else 543 | RegisterLuaAnimation(name,t) 544 | 545 | 546 | animName = name 547 | animationData = GetLuaAnimations()[animName] 548 | animType = animationData.Type 549 | frame:Remove() 550 | AnimationStarted(true) 551 | LocalPlayer():StopAllLuaAnimations() 552 | end 553 | end 554 | frame:Center() 555 | end 556 | local function RegisterAll() 557 | 558 | 559 | for i,v in pairs(file.Find("animations/*.txt", "DATA")) do 560 | local str = file.Read("animations/"..string.sub(v,1,-5)..".txt", "DATA") 561 | if !str then return end 562 | local success,t = pcall(Deserialize, str) 563 | if !success then 564 | ErrorNoHalt("WARNING: Animation '"..string.sub(v,1,-5).."' failed to load\n") 565 | else 566 | RegisterLuaAnimation(string.sub(v,1,-5),t) 567 | end 568 | end 569 | subAnims:Refresh() 570 | 571 | end 572 | 573 | 574 | 575 | local function SaveAnimation() 576 | if(!file.Exists("animations","DATA")) then file.CreateDir"animations" end 577 | 578 | Derma_StringRequest( "Question", 579 | "Save as...", 580 | animName or "", 581 | function( strTextOut ) RegisterLuaAnimation(strTextOut,animationData) file.Write("animations/"..strTextOut..".txt", util.TableToJSON(animationData)) end, 582 | function( strTextOut ) end, 583 | "Save", 584 | "Cancel" ) 585 | 586 | 587 | 588 | 589 | 590 | 591 | end 592 | 593 | 594 | local topLevelPanels = {} 595 | local function IsMouseOverPanel() 596 | 597 | local mouseX = gui.MouseX() 598 | local mouseY = gui.MouseY() 599 | for i,v in pairs(topLevelPanels) do 600 | if ValidPanel(v) && v:IsVisible() then 601 | 602 | local bChild = IsChildOfHiddenParent(v) 603 | if !bChild then 604 | 605 | local x,y = v:GetPos() 606 | local w = v:GetWide() 607 | local h = v:GetTall() 608 | local overX = mouseX > x && mouseX < x+w 609 | local overY = mouseY > y && mouseY < y+h 610 | if overX && overY then return true end 611 | end 612 | else 613 | table.remove(topLevelPanels,i) 614 | end 615 | end 616 | return false 617 | end 618 | 619 | 620 | local function FixMouse() 621 | if !animating then return end 622 | local notOverPanel = !IsMouseOverPanel() 623 | 624 | if !input.IsMouseDown(MOUSE_RIGHT) && rightDown then 625 | rightDown = false 626 | elseif input.IsMouseDown(MOUSE_RIGHT) && !rightDown && notOverPanel then 627 | rightDown = true 628 | pressedPos[1] = gui.MouseX() 629 | pressedPos[2] = gui.MouseY() 630 | 631 | elseif input.IsMouseDown(MOUSE_RIGHT) && rightDown then 632 | local mvmtX = gui.MouseX()-pressedPos[1] 633 | angToPlayer.yaw = angToPlayer.yaw + mvmtX 634 | 635 | local mvmtY = (gui.MouseY()-pressedPos[2])*0.1 636 | camHeight = math.max(10,camHeight - mvmtY) 637 | 638 | gui.SetMousePos(pressedPos[1],pressedPos[2]) 639 | end 640 | if input.IsMouseDown(MOUSE_LEFT) && !leftDown && notOverPanel then 641 | 642 | if draggingDir then 643 | pressedPos[1] = gui.MouseX() 644 | pressedPos[2] = gui.MouseY() 645 | leftDown = true 646 | end 647 | 648 | elseif !input.IsMouseDown(MOUSE_LEFT) && leftDown then 649 | leftDown = false 650 | elseif input.IsMouseDown(MOUSE_LEFT) && leftDown then 651 | 652 | local mvmtX = gui.MouseX()-pressedPos[1] 653 | local mvmtY = gui.MouseY()-pressedPos[2] 654 | local dist = math.Distance(gui.MouseX(),gui.MouseY(),pressedPos[1],pressedPos[2]) 655 | if mvmtX < 0 then 656 | dist = dist * -1 657 | end 658 | 659 | sliders:Dragged3D(dist,draggingDir) 660 | 661 | gui.SetMousePos(pressedPos[1],pressedPos[2]) 662 | end 663 | if(input.IsMouseDown(MOUSE_WHEEL_DOWN)) then 664 | print":O" 665 | end 666 | end 667 | 668 | 669 | local function AnimationEditorView(pl,origin,angles,fov) 670 | 671 | 672 | local t = {} 673 | local vec = pl:GetForward()*camDist 674 | 675 | local camTarget = pl:GetPos()+Vector(0,0,camHeight) 676 | 677 | vec:Rotate(angToPlayer) 678 | t.origin=camTarget+vec 679 | t.angles=(camTarget-t.origin):Angle() 680 | return t 681 | 682 | end 683 | 684 | local function AnimationEditorOff() 685 | 686 | for i,v in pairs(animEditorPanels) do 687 | v:Remove() 688 | end 689 | hook.Remove("HUDPaint","PaintTopBar") 690 | hook.Remove("CalcView","AnimationView") 691 | hook.Remove("Think","FixMouse") 692 | hook.Remove("ShouldDrawLocalPlayer","DrawMe") 693 | LocalPlayer():StopAllLuaAnimations() 694 | gui.EnableScreenClicker(false) 695 | animating = false 696 | animName = nil 697 | animationData = nil 698 | animType = nil 699 | editingAnimation = false 700 | end 701 | 702 | local function AnimationEditorOn() 703 | if not LocalPlayer():IsSuperAdmin() and not game.SinglePlayer() then return end 704 | 705 | if animating then AnimationEditorOff() return end 706 | for i,v in pairs(animEditorPanels) do 707 | v:Remove() 708 | end 709 | 710 | 711 | local close = vgui.Create("DButton") 712 | close:SetText("C") 713 | close.DoClick = function(slf) AnimationEditorOff() end 714 | close:SetSize(16,16) 715 | close:SetPos(4,4) 716 | table.insert(animEditorPanels,close) 717 | 718 | timeLine = vgui.Create("AnimEditor_TimeLine") 719 | table.insert(animEditorPanels,timeLine) 720 | 721 | local frame=vgui.Create("DFrame") 722 | frame:SetTitle("Main Menu") 723 | frame:ShowCloseButton(false) 724 | table.insert(animEditorPanels,frame) 725 | mainSettings = vgui.Create("AnimEditor_MainSettings",frame) 726 | frame:SetSize(mainSettings:GetWide(), mainSettings:GetTall()+22) 727 | timer.Simple(0.01, function() frame:SetPos(ScrW()-200,ScrH()-mainSettings:GetTall()-timeLine:GetTall()*1.7) end) 728 | table.insert(animEditorPanels,mainSettings) 729 | 730 | 731 | 732 | 733 | 734 | local sliderFrame = vgui.Create("DFrame") 735 | sliderFrame:ShowCloseButton(false) 736 | sliderFrame:SetTitle("Sliders") 737 | sliderFrame:MakePopup() 738 | sliders = vgui.Create("AnimEditor_Sliders",sliderFrame) 739 | 740 | 741 | table.insert(animEditorPanels,sliderFrame) 742 | 743 | 744 | subAnims = vgui.Create("AnimEditor_SubAnimations") 745 | 746 | 747 | 748 | table.insert(animEditorPanels,subAnims) 749 | 750 | hook.Add("HUDPaint","PaintTopBar",PaintTopBar) 751 | hook.Add("CalcView","AnimationView",AnimationEditorView) 752 | hook.Add("Think","FixMouse",FixMouse) 753 | hook.Add("ShouldDrawLocalPlayer","DrawMe",function() return true end) 754 | gui.EnableScreenClicker(true) 755 | 756 | animating = true 757 | end 758 | concommand.Add("animate",AnimationEditorOn) 759 | 760 | local secondDistance = 200 --100px per second on timeline 761 | 762 | 763 | 764 | local MAIN = {} 765 | function MAIN:Init() 766 | 767 | self:SetName("Main Settings") 768 | self:SetSize(200,315) 769 | self:SetPos(0,22) 770 | 771 | local newanim = self:Button("New Animation") 772 | newanim.DoClick = NewAnimation 773 | 774 | 775 | local loadanim = self:Button("Load Registered Animation") 776 | loadanim.DoClick = LoadAnimation 777 | 778 | local loadanim = self:Button("Load Animation From File") 779 | loadanim.DoClick = LoadAnimationFromFile 780 | 781 | local saveanim = self:Button("Save Animation To File") 782 | saveanim.DoClick = SaveAnimation 783 | 784 | local register = self:Button("Register All Animations") 785 | register.DoClick = RegisterAll 786 | 787 | local viewcode = self:Button("Copy Raw Lua To Clipboard") 788 | viewcode.DoClick = function() local str = OutputCode() if !str then return end SetClipboardText(str) end 789 | 790 | local distSlider = self:NumSlider("Cam Distance", nil, 40, 200, 0 ) 791 | distSlider:SetValue(200) 792 | distSlider.OnValueChanged = function(s,v) camDist = v end 793 | 794 | local boneSet = self:ComboBox("Bone Set") 795 | for i,v in pairs(boneList) do 796 | boneSet:AddChoice(i) 797 | end 798 | boneSet:SetText(selectedBoneSet) 799 | boneSet.OnSelect = function(s,i,v,d) selectedBoneSet = v self:RefreshBoneSet() end 800 | 801 | self.bones = self:ComboBox("Selected Bone") 802 | self.bones:SetTall(200) 803 | --self.bones:SetMultiple(false) 804 | self.bones.OnSelect = function(me, index, value, data) 805 | selectedBone = value 806 | sliders:SetFrameData() 807 | end 808 | 809 | self:RefreshBoneSet() 810 | 811 | 812 | end 813 | 814 | function MAIN:RefreshBoneSet() 815 | if not boneList[selectedBoneSet] then return end 816 | 817 | self.bones:Clear() 818 | 819 | for i, v in pairs(boneList[selectedBoneSet]) do 820 | --self.bones:AddItem(v).DoClick = function(s) selectedBone = s:GetValue() sliders:SetFrameData() end 821 | local id = self.bones:AddChoice(v) 822 | end 823 | end 824 | 825 | vgui.Register("AnimEditor_MainSettings", MAIN, "DForm") 826 | 827 | local firstPass = true 828 | local TIMELINE = {} 829 | function TIMELINE:Init() 830 | 831 | self:SetTitle("Timeline") 832 | self:ShowCloseButton(false) 833 | self:SetSize(ScrW(),150) 834 | self:SetPos(0,ScrH()-150) 835 | self:SetDraggable(false) 836 | 837 | local timeLine = vgui.Create("DHorizontalScroller",self) 838 | timeLine:SetPos(5,45) 839 | timeLine:SetSize(self:GetWide()-self:GetTall()-30,20) 840 | self.timeLine = timeLine 841 | 842 | 843 | self.subAnims = vgui.Create("DPanelList",self) 844 | self.subAnims:SetSize(timeLine:GetWide(),self:GetTall()-75) 845 | self.subAnims:SetPos(5,50+timeLine:GetTall()) 846 | self.subAnims:EnableVerticalScrollbar() 847 | local timeLineTop = vgui.Create("DPanel",self) 848 | timeLineTop:SetPos(5,25) 849 | timeLineTop:SetSize(self:GetWide()-self:GetTall(),20) 850 | timeLineTop.Paint = function(s) 851 | 852 | 853 | 854 | local XPos = timeLine.OffsetX 855 | 856 | draw.RoundedBox(0,0,0,self:GetWide(),16,Color(200,200,200,255)) 857 | 858 | if animName then 859 | if playingAnimation then 860 | playBarOffset = playBarOffset + FrameTime()*secondDistance 861 | end 862 | 863 | 864 | local subtraction = 0 865 | if firstPass && animationData.StartFrame then 866 | for i=1,animationData.StartFrame do 867 | local v = animationData.FrameData[i] 868 | subtraction = subtraction+(1/(v.FrameRate or 1)) 869 | end 870 | elseif !firstPass && animationData.RestartFrame then 871 | for i=1,animationData.RestartFrame do 872 | local v = animationData.FrameData[i] 873 | subtraction = subtraction+(1/(v.FrameRate or 1)) 874 | end 875 | end 876 | 877 | 878 | if (playBarOffset-subtraction)/secondDistance > self:GetAnimationTime() then 879 | local restartPos = self:ResolveRestart() 880 | playBarOffset = restartPos*secondDistance 881 | end 882 | draw.RoundedBox(0,playBarOffset-1,0,2,16,Color(255,0,0,240)) 883 | end 884 | 885 | local previousSecond = XPos-(XPos%secondDistance) 886 | for i=previousSecond,previousSecond+s:GetWide(),secondDistance/4 do 887 | if i-XPos > 0 && i-XPos < ScrW() then 888 | local sec = i/secondDistance 889 | draw.SimpleText(sec,"DefaultFontSmall",i-XPos,6,Color(0,0,0,255),1,1) 890 | end 891 | end 892 | 893 | end 894 | 895 | 896 | 897 | local addKeyButton = vgui.Create("DButton",self) 898 | addKeyButton:SetText("Add KeyFrame") 899 | addKeyButton.DoClick = function() self:AddKeyFrame() end 900 | addKeyButton:SetSize(self:GetTall()-20,self:GetTall()-60) 901 | addKeyButton:SetPos(self:GetWide()-self:GetTall()+10,30) 902 | self.addKeyButton = addKeyButton 903 | addKeyButton:SetDisabled(true) 904 | 905 | self.isPlaying = false 906 | local play = vgui.Create("DButton",self) 907 | play:SetPos(self:GetWide()-self:GetTall()+10,self:GetTall()-25) 908 | play:SetWide(self:GetTall()-60) 909 | play:SetText("Play") 910 | play.DoClick = function() 911 | self:Toggle() 912 | 913 | 914 | end 915 | self.play = play 916 | self.play:SetDisabled(true) 917 | 918 | end 919 | function TIMELINE:Toggle(bForce) 920 | if bForce != nil then 921 | self.isPlaying = bForce 922 | else 923 | self.isPlaying = !self.isPlaying 924 | end 925 | if self.isPlaying then 926 | 927 | 928 | RegisterLuaAnimation("editortest",animationData) 929 | LocalPlayer():StopAllLuaAnimations() 930 | LocalPlayer():SetLuaAnimation("editortest") 931 | for i,v in pairs(subAnimationsLoaded) do 932 | LocalPlayer():SetLuaAnimation(i) 933 | end 934 | 935 | playingAnimation = true 936 | playBarOffset = self:ResolveStart()*secondDistance 937 | 938 | self.play:SetText("Stop") 939 | 940 | for i,v in pairs(subAnimationsLoaded) do 941 | v.subPlayBarOffset = v.storedTimeTillStart 942 | end 943 | else 944 | 945 | 946 | LocalPlayer():StopAllLuaAnimations() 947 | playingAnimation = false 948 | 949 | 950 | playBarOffset = self:ResolveStart()*secondDistance 951 | self.play:SetText("Play") 952 | for i,v in pairs(subAnimationsLoaded) do 953 | v.subPlayBarOffset = v.storedTimeTillStart 954 | end 955 | end 956 | 957 | end 958 | 959 | function TIMELINE:OnNewAnimation() 960 | for i,v in pairs(self.timeLine.Panels) do 961 | v:Remove() 962 | self.timeLine.Panels[i] = nil 963 | end 964 | self.addKeyButton:SetDisabled(false) 965 | self.play:SetDisabled(false) 966 | self:AddKeyFrame() --helper add first frame 967 | end 968 | local addFrame = true 969 | function TIMELINE:OnLoadAnimation() 970 | for i,v in pairs(self.timeLine.Panels) do 971 | v:Remove() 972 | self.timeLine.Panels[i] = nil 973 | end 974 | self.addKeyButton:SetDisabled(false) 975 | self.play:SetDisabled(false) 976 | 977 | 978 | addFrame = false 979 | for i,v in pairs(animationData.FrameData) do 980 | 981 | local keyframe = self:AddKeyFrame() --helper add first frame 982 | keyframe:SetFrameData(i,v) 983 | 984 | end 985 | addFrame = true 986 | 987 | end 988 | local flip = false 989 | function TIMELINE:LoadSubAnimation(name) 990 | 991 | local anim = GetLuaAnimations()[name] 992 | if !anim then return end 993 | 994 | if subAnimationsLoaded[name] then 995 | self.subAnims:RemoveItem(subAnimationsLoaded[name]) 996 | subAnimationsLoaded[name] = nil 997 | else 998 | flip = !flip 999 | local timeLine = vgui.Create("DHorizontalScroller") 1000 | timeLine:SetPos(5,45) 1001 | timeLine:SetSize(self:GetWide()-self:GetTall()-30,20) 1002 | 1003 | 1004 | 1005 | local dataCache = {} --holds key frame size for sub anims 1006 | timeLine.subPlayBarOffset = 0 1007 | 1008 | local tempFlip = flip 1009 | local start = anim.StartFrame or 1 1010 | local restart = anim.RestartFrame or 1 1011 | local restartPos = 0 1012 | local totalAnimationTime = 0 1013 | local firstPass = true 1014 | 1015 | for i,v in ipairs(anim.FrameData) do 1016 | 1017 | local frameLen = 1/(v.FrameRate or 1) 1018 | if anim.StartFrame && anim.StartFrame > i then 1019 | timeLine.subPlayBarOffset = timeLine.subPlayBarOffset + frameLen*secondDistance 1020 | end 1021 | if anim.RestartFrame && anim.RestartFrame > i then 1022 | restartPos = restartPos + frameLen 1023 | end 1024 | totalAnimationTime = totalAnimationTime + frameLen 1025 | table.insert(dataCache,secondDistance/v.FrameRate) 1026 | end 1027 | timeLine.storedTimeTillStart = timeLine.subPlayBarOffset 1028 | 1029 | 1030 | timeLine.Paint = function(s) 1031 | local XPos = self.timeLine.OffsetX 1032 | 1033 | 1034 | local total = 0 1035 | local drawnName = false 1036 | 1037 | 1038 | for i,v in ipairs(dataCache) do 1039 | 1040 | local col 1041 | if i%2 == 0 then 1042 | if tempFlip then 1043 | col = Color(200,200,200,255) 1044 | else 1045 | col = Color(150,150,150,255) 1046 | end 1047 | else 1048 | if tempFlip then 1049 | col = Color(150,150,150,255) 1050 | else 1051 | col = Color(200,200,200,255) 1052 | end 1053 | end 1054 | local leftStart = total-XPos 1055 | draw.RoundedBox(0,leftStart,0,v,self:GetTall(),col) 1056 | 1057 | draw.SimpleText(name,"DefaultFontSmall",leftStart+20,5,Color(0,0,0,255),0,3) 1058 | draw.SimpleText(i,"DefaultFontSmall",total-XPos+5,5,Color(0,0,0,255),0,3) 1059 | local rightBound = leftStart+v 1060 | if restart != 1 && restart == i then 1061 | draw.SimpleText("Restart","DefaultFontSmall",rightBound-30,5,Color(0,0,0,255),2,3) 1062 | end 1063 | if start != 1 && start == i then 1064 | draw.SimpleText("Start","DefaultFontSmall",rightBound-25,5,Color(0,0,0,255),0,3) 1065 | end 1066 | total = total + v 1067 | 1068 | end 1069 | 1070 | 1071 | 1072 | 1073 | if playingAnimation then 1074 | timeLine.subPlayBarOffset = timeLine.subPlayBarOffset + FrameTime()*secondDistance 1075 | end 1076 | 1077 | 1078 | local subtraction = 0 1079 | if firstPass && animationData.StartFrame then 1080 | subtraction = timeLine.storedTimeTillStart 1081 | firstPass = false 1082 | elseif !firstPass && animationData.RestartFrame then 1083 | for i=1,animationData.RestartFrame do 1084 | subtraction = restartPos 1085 | end 1086 | end 1087 | 1088 | 1089 | if (timeLine.subPlayBarOffset-subtraction)/secondDistance > totalAnimationTime then 1090 | timeLine.subPlayBarOffset = restartPos*secondDistance 1091 | end 1092 | draw.RoundedBox(0,timeLine.subPlayBarOffset-1,0,2,16,Color(0,255,0,240)) 1093 | 1094 | end 1095 | self.subAnims:AddItem(timeLine) 1096 | 1097 | 1098 | 1099 | 1100 | subAnimationsLoaded[name] = timeLine 1101 | end 1102 | 1103 | 1104 | end 1105 | 1106 | 1107 | 1108 | 1109 | function TIMELINE:GetAnimationTime() 1110 | 1111 | local tempTime = 0 1112 | local globalAnims = GetLuaAnimations() 1113 | local startIndex = 1 1114 | 1115 | if animationData and animationData.FrameData then 1116 | for i=startIndex, #animationData.FrameData do 1117 | local v = animationData.FrameData[i] 1118 | tempTime = tempTime+(1/(v.FrameRate or 1)) 1119 | end 1120 | end 1121 | 1122 | 1123 | 1124 | return tempTime 1125 | 1126 | end 1127 | 1128 | function TIMELINE:ResolveRestart() --get restart pos in seconds 1129 | firstPass = false 1130 | local timeInSeconds = 0 1131 | local restartFrame = animationData.RestartFrame 1132 | if !restartFrame then return 0 end --no restart pos? start at the start 1133 | 1134 | for i,v in pairs(animationData.FrameData) do 1135 | if i == restartFrame then return timeInSeconds end 1136 | timeInSeconds = timeInSeconds+(1/(v.FrameRate or 1)) 1137 | end 1138 | 1139 | end 1140 | 1141 | function TIMELINE:ResolveStart() --get restart pos in seconds 1142 | firstPass = true 1143 | local timeInSeconds = 0 1144 | local startFrame = animationData.StartFrame 1145 | if !startFrame then return 0 end --no restart pos? start at the start 1146 | 1147 | for i,v in pairs(animationData.FrameData) do 1148 | if i == startFrame then return timeInSeconds end 1149 | timeInSeconds = timeInSeconds+(1/(v.FrameRate or 1)) 1150 | end 1151 | 1152 | end 1153 | 1154 | local flippedBool = false 1155 | function TIMELINE:AddKeyFrame() 1156 | flippedBool = !flippedBool 1157 | local keyframe = vgui.Create("AnimEditor_KeyFrame") 1158 | keyframe:SetWide(secondDistance) --default to 1 second animations 1159 | 1160 | keyframe.Alternate = flippedBool 1161 | 1162 | 1163 | --[[if keyframe:GetAnimationIndex() && keyframe:GetAnimationIndex() > 1 then 1164 | keyframe:CopyPreviousKey() 1165 | end]] 1166 | 1167 | self.timeLine:AddPanel(keyframe) 1168 | self.timeLine:InvalidateLayout() 1169 | 1170 | 1171 | 1172 | if animType == TYPE_POSTURE then self.addKeyButton:SetDisabled(true) end --postures have only one keyframe 1173 | 1174 | return keyframe 1175 | 1176 | end 1177 | vgui.Register("AnimEditor_TimeLine",TIMELINE,"DFrame") 1178 | 1179 | local KEYFRAME = {} 1180 | 1181 | function KEYFRAME:Init() 1182 | self:SetWide(secondDistance) 1183 | if addFrame then 1184 | self.AnimationKeyIndex = table.insert(animationData.FrameData,{FrameRate = 1,BoneInfo = {}}) 1185 | self.DataTable = animationData.FrameData[self.AnimationKeyIndex] 1186 | end 1187 | selectedFrame = self 1188 | end 1189 | function KEYFRAME:GetData() 1190 | return self.DataTable 1191 | end 1192 | function KEYFRAME:SetFrameData(index,tbl) 1193 | self.DataTable = tbl 1194 | self.AnimationKeyIndex = index 1195 | self:SetWide(1/self:GetData().FrameRate*secondDistance) 1196 | self:GetParent():GetParent():InvalidateLayout() --rebuild the timeline 1197 | if animationData.RestartFrame == index then 1198 | self.RestartPos = true 1199 | end 1200 | if animationData.StartFrame == index then 1201 | self.StartPos = true 1202 | end 1203 | end 1204 | function KEYFRAME:CopyPreviousKey() 1205 | local iKeyIndex = self:GetAnimationIndex()-1 1206 | local tFrameData = table.Copy(animationData.FrameData[iKeyIndex]) 1207 | if !tFrameData then return end 1208 | 1209 | 1210 | 1211 | end 1212 | function KEYFRAME:GetAnimationIndex() 1213 | return self.AnimationKeyIndex 1214 | end 1215 | function KEYFRAME:Paint() 1216 | local col = Color(150,150,150,255) 1217 | if self.Alternate then 1218 | col = Color(200,200,200,255) 1219 | end 1220 | draw.RoundedBox(0,0,0,self:GetWide(),self:GetTall(),col) 1221 | if selectedFrame == self then 1222 | surface.SetDrawColor(255,0,0,255) 1223 | surface.DrawOutlinedRect(1,1,self:GetWide()-2,self:GetTall()-2) 1224 | end 1225 | draw.SimpleText(self:GetAnimationIndex(),"DefaultFontSmall",5,5,Color(0,0,0,255),0,3) 1226 | if self.RestartPos then 1227 | draw.SimpleText("Restart","DefaultFontSmall",self:GetWide()-30,5,Color(0,0,0,255),2,3) 1228 | end 1229 | if self.StartPos then 1230 | draw.SimpleText("Start","DefaultFontSmall",self:GetWide()-25,5,Color(0,0,0,255),0,3) 1231 | end 1232 | end 1233 | function KEYFRAME:OnMousePressed(mc) 1234 | if mc == MOUSE_LEFT then 1235 | timeLine:Toggle(false) 1236 | selectedFrame = self 1237 | sliders:SetFrameData() 1238 | ApplyEndResults() 1239 | elseif mc == MOUSE_RIGHT then 1240 | local menu = DermaMenu() 1241 | menu:AddOption("Change Frame Length",function() 1242 | Derma_StringRequest( "Question", 1243 | "How long should this frame be (seconds)?", 1244 | "1.0", 1245 | function( strTextOut ) self:SetLength(tonumber(strTextOut)) end, 1246 | function( strTextOut ) end, 1247 | "Set Length", 1248 | "Cancel" ) 1249 | end) 1250 | menu:AddOption("Change Frame Rate",function() 1251 | Derma_StringRequest( "Question", 1252 | "Set frame "..self:GetAnimationIndex().."'s framerate", 1253 | "1.0", 1254 | function( strTextOut ) self:SetLength(1/tonumber(strTextOut)) end, 1255 | function( strTextOut ) end, 1256 | "Set Frame Rate", 1257 | "Cancel" ) 1258 | end) 1259 | if animationData.Type != TYPE_GESTURE then 1260 | menu:AddOption("Set Restart Pos",function() 1261 | 1262 | for i,v in pairs(timeLine.timeLine.Panels) do 1263 | if v.RestartPos then v.RestartPos = nil end 1264 | end 1265 | self.RestartPos = true 1266 | animationData.RestartFrame = self:GetAnimationIndex() 1267 | end) 1268 | end 1269 | if animationData.Type == TYPE_SEQUENCE then 1270 | menu:AddOption("Set Start Pos",function() 1271 | 1272 | for i,v in pairs(timeLine.timeLine.Panels) do 1273 | if v.StartPos then v.StartPos = nil end 1274 | end 1275 | self.StartPos = true 1276 | animationData.StartFrame = self:GetAnimationIndex() 1277 | end) 1278 | end 1279 | 1280 | 1281 | 1282 | if self:GetAnimationIndex() > 1 then 1283 | menu:AddOption("Reverse Previous Frame",function() 1284 | local tbl = animationData.FrameData[self:GetAnimationIndex() - 1].BoneInfo 1285 | for i, v in pairs(tbl) do 1286 | self:GetData().BoneInfo[i] = table.Copy(self:GetData().BoneInfo[i] or {}) 1287 | self:GetData().BoneInfo[i].MU = v.MU * -1 1288 | self:GetData().BoneInfo[i].MR = v.MR * -1 1289 | self:GetData().BoneInfo[i].MF = v.MF * -1 1290 | self:GetData().BoneInfo[i].RU = v.RU * -1 1291 | self:GetData().BoneInfo[i].RR = v.RR * -1 1292 | self:GetData().BoneInfo[i].RF = v.RF * -1 1293 | end 1294 | sliders:SetFrameData() 1295 | end) 1296 | end 1297 | 1298 | menu:AddOption("Duplicate Frame To End", function() 1299 | local keyframe = timeLine:AddKeyFrame() 1300 | 1301 | local tbl = self:GetData().BoneInfo 1302 | for i, v in pairs(tbl) do 1303 | local data = keyframe:GetData() 1304 | data.BoneInfo[i] = table.Copy(self:GetData().BoneInfo[i] or {}) 1305 | data.BoneInfo[i].MU = v.MU 1306 | data.BoneInfo[i].MR = v.MR 1307 | data.BoneInfo[i].MF = v.MF 1308 | data.BoneInfo[i].RU = v.RU 1309 | data.BoneInfo[i].RR = v.RR 1310 | data.BoneInfo[i].RF = v.RF 1311 | end 1312 | sliders:SetFrameData() 1313 | 1314 | --[[local tbl = animationData.FrameData 1315 | local keyframe = timeLine:AddKeyFrame() 1316 | keyframe.DataTable = table.Copy(self:GetData() or {}) 1317 | selectedFrame = keyframe 1318 | sliders:SetFrameData()]] 1319 | end) 1320 | 1321 | 1322 | menu:AddOption("Remove Frame",function() 1323 | local frameNum = self:GetAnimationIndex() 1324 | if frameNum == 1 and !animationData.FrameData[2] then return end --can't delete the frame when it's the only one 1325 | table.remove(animationData.FrameData,frameNum) 1326 | for i,v in pairs(timeLine.timeLine.Panels) do 1327 | if v == self then 1328 | timeLine.timeLine.Panels[i] = nil 1329 | elseif v:GetAnimationIndex() > frameNum then 1330 | v.AnimationKeyIndex = v.AnimationKeyIndex - 1 1331 | v.Alternate = !v.Alternate 1332 | end 1333 | end 1334 | 1335 | timeLine.timeLine:InvalidateLayout() 1336 | self:Remove() 1337 | 1338 | end) 1339 | 1340 | menu:Open() 1341 | 1342 | end 1343 | end 1344 | function KEYFRAME:SetLength(int) 1345 | if !int then return end 1346 | self:SetWide(secondDistance*int) 1347 | self:GetParent():GetParent():InvalidateLayout() --rebuild the timeline 1348 | self:GetData().FrameRate = 1/int --set animation frame rate 1349 | end 1350 | vgui.Register("AnimEditor_KeyFrame",KEYFRAME,"DPanel") 1351 | 1352 | 1353 | local SLIDERS = {} 1354 | function SLIDERS:Init() 1355 | self:SetName("Modify Bone") 1356 | self:SetWide(200) 1357 | self.Sliders = {} 1358 | 1359 | self.Sliders.MU = self:NumSlider("Translate UP", nil, -100, 100, 0 ) 1360 | self.Sliders.MU.OnValueChanged = function(s,v) self:OnSliderChanged("MU",v) end 1361 | self.Sliders.MU.Label:SetTextColor(Color(0,0,255,255)) 1362 | 1363 | local oldEnter = self.Sliders.MU.Wang.OnEnter 1364 | self.Sliders.MU.Wang.OnEnter = function(s) self:OnSliderChanged("MU",self.Sliders.MU.Wang:GetValue()) self.Sliders.MU.Slider:InvalidateLayout() oldEnter(s) end 1365 | 1366 | self.Sliders.MR = self:NumSlider("Translate RIGHT", nil, -100, 100, 0 ) 1367 | self.Sliders.MR.OnValueChanged = function(s,v) self:OnSliderChanged("MR",v) end 1368 | self.Sliders.MR.Label:SetTextColor(Color(255,0,0,255)) 1369 | self.Sliders.MR.Wang.OnEnter = function(s) self:OnSliderChanged("MR",self.Sliders.MR.Wang:GetValue()) self.Sliders.MR.Slider:InvalidateLayout() oldEnter(s) end 1370 | 1371 | self.Sliders.MF = self:NumSlider("Translate FORWARD", nil, -100, 100, 0 ) 1372 | self.Sliders.MF.OnValueChanged = function(s,v) self:OnSliderChanged("MF",v) end 1373 | self.Sliders.MF.Label:SetTextColor(Color(0,255,0,255)) 1374 | self.Sliders.MF.Wang.OnEnter = function(s) self:OnSliderChanged("MF",self.Sliders.MF.Wang:GetValue()) self.Sliders.MF.Slider:InvalidateLayout() oldEnter(s) end 1375 | 1376 | self.Sliders.RU = self:NumSlider("Rotate UP", nil, -360, 360, 0 ) 1377 | self.Sliders.RU.OnValueChanged = function(s,v) self:OnSliderChanged("RU",v) end 1378 | self.Sliders.RU.Label:SetTextColor(Color(0,255,0,255)) 1379 | self.Sliders.RU.Wang.OnEnter = function(s) self:OnSliderChanged("RU",self.Sliders.RU.Wang:GetValue()) self.Sliders.RU.Slider:InvalidateLayout() oldEnter(s) end 1380 | 1381 | self.Sliders.RR = self:NumSlider("Rotate RIGHT", nil, -360, 360, 0 ) 1382 | self.Sliders.RR.OnValueChanged = function(s,v) self:OnSliderChanged("RR",v) end 1383 | self.Sliders.RR.Label:SetTextColor(Color(255,0,0,255)) 1384 | self.Sliders.RR.Wang.OnEnter = function(s) self:OnSliderChanged("RR",self.Sliders.RR.Wang:GetValue()) self.Sliders.RR.Slider:InvalidateLayout() oldEnter(s) end 1385 | 1386 | self.Sliders.RF = self:NumSlider("Rotate FORWARD", nil, -360, 360, 0 ) 1387 | self.Sliders.RF.OnValueChanged = function(s,v) self:OnSliderChanged("RF",v) end 1388 | self.Sliders.RF.Label:SetTextColor(Color(0,0,255,255)) 1389 | self.Sliders.RF.Wang.OnEnter = function(s) self:OnSliderChanged("RF",self.Sliders.RF.Wang:GetValue()) self.Sliders.RF.Slider:InvalidateLayout() oldEnter(s) end 1390 | --self:GetParent():MakePopup() 1391 | --self:GetParent():KillFocus() 1392 | --self:GetParent():SetKeyboardInputEnabled(false) 1393 | --self:GetParent():SetMouseInputEnabled(false) 1394 | 1395 | timer.Simple(0.01,function() 1396 | local x,y = self:GetSize() 1397 | self:GetParent():SetSize(x+10,y+200) 1398 | self:SetPos(5,25) 1399 | self:GetParent():SetPos(0,ScrH()-timeLine:GetTall()-self:GetParent():GetTall()) 1400 | x,y = self:GetParent():GetPos() 1401 | subAnims:SetPos(0,y-subAnims:GetTall()) 1402 | 1403 | 1404 | end) 1405 | end 1406 | local needsUpdate = true 1407 | function SLIDERS:SetFrameData() 1408 | --print(selectedFrame,selectedBone,selectedFrame:GetData().BoneInfo[selectedBone]) 1409 | needsUpdate = false 1410 | if !ValidPanel(selectedFrame) || !selectedBone || !selectedFrame:GetData().BoneInfo[selectedBone] then 1411 | 1412 | for i,v in pairs(self.Sliders) do 1413 | v:SetValue(0) 1414 | end 1415 | needsUpdate = true 1416 | return end 1417 | 1418 | for i,v in pairs(self.Sliders) do 1419 | v:SetValue(selectedFrame:GetData().BoneInfo[selectedBone][i] or 0) 1420 | end 1421 | needsUpdate = true 1422 | end 1423 | function SLIDERS:Dragged3D(changeAmt,moveType) 1424 | local ChangeAmt = math.Clamp(self.Sliders[moveType]:GetValue()+changeAmt,-360,360) 1425 | if ChangeAmt == self.Sliders[moveType]:GetValue() then return end 1426 | self.Sliders[moveType]:SetValue(ChangeAmt) 1427 | end 1428 | function SLIDERS:OnSliderChanged(moveType,value) 1429 | if !ValidPanel(selectedFrame) || !table.HasValue(boneList[selectedBoneSet],selectedBone) then return end --no keyframe/bone selected 1430 | if (tonumber(value) == 0 && selectedFrame:GetData().BoneInfo[selectedBone] == nil) || !needsUpdate then return end 1431 | 1432 | --[[if selectedFrame:GetAnimationIndex() > 1 then 1433 | local prevBoneData = animationData.FrameData[self:GetAnimationIndex()-1][selectedBone] 1434 | if prevBoneData then]] 1435 | 1436 | 1437 | selectedFrame:GetData().BoneInfo = selectedFrame:GetData().BoneInfo or {} 1438 | selectedFrame:GetData().BoneInfo[selectedBone] = selectedFrame:GetData().BoneInfo[selectedBone] or {} 1439 | selectedFrame:GetData().BoneInfo[selectedBone][moveType] = tonumber(value) 1440 | ApplyEndResults() 1441 | 1442 | 1443 | end 1444 | vgui.Register("AnimEditor_Sliders",SLIDERS,"DForm") 1445 | 1446 | local SUBANIMS = {} 1447 | function SUBANIMS:Init() 1448 | self:SetSize(210,120) 1449 | self:ShowCloseButton(false) 1450 | self.AnimList = vgui.Create("DComboBox",self) 1451 | self.AnimList:StretchToParent(5,25,5,30) 1452 | self:SetTitle("Sub Animations") 1453 | self.SelectedAnim = "" 1454 | self.AnimList.OnSelect = function(me, id, value, data) 1455 | self.SelectedAnim = value 1456 | if subAnimationsLoaded[value] then 1457 | self.AddButton:SetText("Remove Animation") 1458 | else 1459 | self.AddButton:SetText("Add Animation") 1460 | end 1461 | end 1462 | 1463 | self.AddButton = vgui.Create("DButton",self) 1464 | self.AddButton:SetPos(5,self:GetTall()-25) 1465 | self.AddButton:SetSize(self:GetWide()-10,20) 1466 | self.AddButton.DoClick = function() timeLine:LoadSubAnimation(self.SelectedAnim,self.AnimList) 1467 | if subAnimationsLoaded[i] then 1468 | self.AddButton:SetText("Remove Animation") 1469 | else 1470 | self.AddButton:SetText("Add Animation") 1471 | end 1472 | end 1473 | self.AddButton:SetText("Click an Animation...") 1474 | 1475 | self:Refresh() 1476 | 1477 | end 1478 | function SUBANIMS:Refresh() 1479 | self.AnimList:Clear() 1480 | for i,v in pairs(GetLuaAnimations()) do 1481 | 1482 | --no need to show these 1483 | if i != "editortest" && i != animName && i != "editingAnim" && !string.find(i,"subPosture_") then 1484 | local item = self.AnimList:AddChoice(i) 1485 | --[[local item = self.AnimList:AddItem(i) 1486 | item.DoClick = function() 1487 | self.SelectedAnim = i 1488 | if subAnimationsLoaded[i] then 1489 | self.AddButton:SetText("Remove Animation") 1490 | else 1491 | self.AddButton:SetText("Add Animation") 1492 | end 1493 | 1494 | end]] 1495 | end 1496 | end 1497 | 1498 | end 1499 | vgui.Register("AnimEditor_SubAnimations",SUBANIMS,"DFrame") 1500 | -------------------------------------------------------------------------------- /cl_boneanimlib.lua: -------------------------------------------------------------------------------- 1 | if SERVER or GetLuaAnimations ~= nil then return end 2 | 3 | include("sh_boneanimlib.lua") 4 | include("sh_mocap.lua") 5 | 6 | local ANIMATIONFADEOUTTIME = 0.125 7 | 8 | net.Receive("bal_reset", function(length) 9 | local ent = net.ReadEntity() 10 | local anim = net.ReadString() 11 | local time = net.ReadFloat() 12 | local power = net.ReadFloat() 13 | local timescale = net.ReadFloat() 14 | 15 | if ent:IsValid() then 16 | ent:ResetLuaAnimation(anim, time ~= -1 and time, power ~= -1 and power, timescale ~= -1 and timescale) 17 | end 18 | end) 19 | 20 | net.Receive("bal_set", function(length) 21 | local ent = net.ReadEntity() 22 | local anim = net.ReadString() 23 | local time = net.ReadFloat() 24 | local power = net.ReadFloat() 25 | local timescale = net.ReadFloat() 26 | 27 | if ent:IsValid() then 28 | ent:SetLuaAnimation(anim, time ~= -1 and time, power ~= -1 and power, timescale ~= -1 and timescale) 29 | end 30 | end) 31 | 32 | net.Receive("bal_stop", function(length) 33 | local ent = net.ReadEntity() 34 | local anim = net.ReadString() 35 | local tim = net.ReadFloat() 36 | 37 | if tim == 0 then tim = nil end 38 | if ent:IsValid() then 39 | ent:StopLuaAnimation(anim, tim) 40 | end 41 | end) 42 | 43 | net.Receive("bal_stopgroup", function(length) 44 | local ent = net.ReadEntity() 45 | local animgroup = net.ReadString() 46 | local tim = net.ReadFloat() 47 | 48 | if tim == 0 then tim = nil end 49 | if ent:IsValid() then 50 | ent:StopLuaAnimationGroup(animgroup, tim) 51 | end 52 | end) 53 | 54 | net.Receive("bal_stopall", function(length) 55 | local ent = net.ReadEntity() 56 | local tim = net.ReadFloat() 57 | 58 | if tim == 0 then tim = nil end 59 | if ent:IsValid() then 60 | ent:StopAllLuaAnimations(tim) 61 | end 62 | end) 63 | 64 | local TYPE_GESTURE = TYPE_GESTURE 65 | local TYPE_POSTURE = TYPE_POSTURE 66 | local TYPE_STANCE = TYPE_STANCE 67 | local TYPE_SEQUENCE = TYPE_SEQUENCE 68 | 69 | local Animations = GetLuaAnimations() 70 | 71 | local function AdvanceFrame(tGestureTable, tFrameData) 72 | tGestureTable.FrameDelta = tGestureTable.FrameDelta + FrameTime() * tFrameData.FrameRate * tGestureTable.TimeScale 73 | if tGestureTable.FrameDelta > 1 then 74 | tGestureTable.Frame = tGestureTable.Frame + 1 75 | tGestureTable.FrameDelta = math.min(1, tGestureTable.FrameDelta - 1) 76 | if tGestureTable.Frame > #tGestureTable.FrameData then 77 | tGestureTable.Frame = math.min(tGestureTable.RestartFrame or 1, #tGestureTable.FrameData) 78 | 79 | return true 80 | end 81 | end 82 | 83 | return false 84 | end 85 | 86 | local function LinearInterpolation(y1, y2, mu) 87 | return y1 * (1 - mu) + y2 * mu 88 | end 89 | 90 | local function CosineInterpolation(y1, y2, mu) 91 | local mu2 = (1 - math.cos(mu * math.pi)) / 2 92 | return y1 * (1 - mu2) + y2 * mu2 93 | end 94 | 95 | local function CubicInterpolation(y0, y1, y2, y3, mu) 96 | local mu2 = mu * mu 97 | local a0 = y3 - y2 - y0 + y1 98 | return a0 * mu * mu2 + (y0 - y1 - a0) * mu2 + (y2 - y0) * mu + y1 99 | end 100 | 101 | local EMPTYBONEINFO = {MU = 0, MR = 0, MF = 0, RU = 0, RR = 0, RF = 0} 102 | local function GetFrameBoneInfo(pl, tGestureTable, iFrame, iBoneID) 103 | local tPrev = tGestureTable.FrameData[iFrame] 104 | if tPrev then 105 | return tPrev.BoneInfo[iBoneID] or tPrev.BoneInfo[pl:GetBoneName(iBoneID)] or EMPTYBONEINFO 106 | end 107 | 108 | return EMPTYBONEINFO 109 | end 110 | 111 | local function DoCurrentFrame(tGestureTable, tFrameData, iCurFrame, pl, fAmount, fFrameDelta, fPower, bNoInterp, tBuffer) 112 | for iBoneID, tBoneInfo in pairs(tFrameData.BoneInfo) do 113 | if type(iBoneID) ~= "number" then 114 | iBoneID = pl:LookupBone(iBoneID) 115 | end 116 | if not iBoneID then continue end 117 | 118 | if not tBuffer[iBoneID] then tBuffer[iBoneID] = Matrix() end 119 | local mBoneMatrix = tBuffer[iBoneID] 120 | 121 | local vCurBonePos, aCurBoneAng = mBoneMatrix:GetTranslation(), mBoneMatrix:GetAngles() 122 | if not tBoneInfo.Callback or not tBoneInfo.Callback(pl, mBoneMatrix, iBoneID, vCurBonePos, aCurBoneAng, fFrameDelta, fPower) then 123 | local vUp = aCurBoneAng:Up() 124 | local vRight = aCurBoneAng:Right() 125 | local vForward = aCurBoneAng:Forward() 126 | local iInterp = tGestureTable.Interpolation 127 | 128 | if iInterp == INTERP_LINEAR or bNoInterp then 129 | local bi1 = GetFrameBoneInfo(pl, tGestureTable, iCurFrame - 1, iBoneID) 130 | mBoneMatrix:Translate(LinearInterpolation(bi1.MU * vUp + bi1.MR * vRight + bi1.MF * vForward, tBoneInfo.MU * vUp + tBoneInfo.MR * vRight + tBoneInfo.MF * vForward, fFrameDelta) * fPower) 131 | mBoneMatrix:Rotate(LinearInterpolation(Angle(bi1.RR, bi1.RU, bi1.RF), Angle(tBoneInfo.RR, tBoneInfo.RU, tBoneInfo.RF), fFrameDelta) * fPower) 132 | elseif iInterp == INTERP_CUBIC and tGestureTable.FrameData[iCurFrame - 2] and tGestureTable.FrameData[iCurFrame + 1] then 133 | local bi0 = GetFrameBoneInfo(pl, tGestureTable, iCurFrame - 2, iBoneID) 134 | local bi1 = GetFrameBoneInfo(pl, tGestureTable, iCurFrame - 1, iBoneID) 135 | local bi3 = GetFrameBoneInfo(pl, tGestureTable, iCurFrame + 1, iBoneID) 136 | mBoneMatrix:Translate(CosineInterpolation(bi1.MU * vUp + bi1.MR * vRight + bi1.MF * vForward, tBoneInfo.MU * vUp + tBoneInfo.MR * vRight + tBoneInfo.MF * vForward, fFrameDelta) * fPower) 137 | mBoneMatrix:Rotate(CubicInterpolation(Angle(bi0.RR, bi0.RU, bi0.RF), 138 | Angle(bi1.RR, bi1.RU, bi1.RF), 139 | Angle(tBoneInfo.RR, tBoneInfo.RU, tBoneInfo.RF), 140 | Angle(bi3.RR, bi3.RU, bi3.RF), 141 | fFrameDelta) * fPower) 142 | else -- Default is Cosine 143 | local bi1 = GetFrameBoneInfo(pl, tGestureTable, iCurFrame - 1, iBoneID) 144 | mBoneMatrix:Translate(CosineInterpolation(bi1.MU * vUp + bi1.MR * vRight + bi1.MF * vForward, tBoneInfo.MU * vUp + tBoneInfo.MR * vRight + tBoneInfo.MF * vForward, fFrameDelta) * fPower) 145 | mBoneMatrix:Rotate(CosineInterpolation(Angle(bi1.RR, bi1.RU, bi1.RF), Angle(tBoneInfo.RR, tBoneInfo.RU, tBoneInfo.RF), fFrameDelta) * fPower) 146 | end 147 | end 148 | end 149 | end 150 | 151 | local function BuildBonePositions(pl) 152 | local tBuffer = {} 153 | 154 | local tLuaAnimations = pl.LuaAnimations 155 | for sGestureName, tGestureTable in pairs(tLuaAnimations) do 156 | local iCurFrame = tGestureTable.Frame 157 | local tFrameData = tGestureTable.FrameData[iCurFrame] 158 | local fFrameDelta = tGestureTable.FrameDelta 159 | local fDieTime = tGestureTable.DieTime 160 | local fPower = tGestureTable.Power 161 | if fDieTime and fDieTime - ANIMATIONFADEOUTTIME <= CurTime() then 162 | fPower = fPower * (fDieTime - CurTime()) / ANIMATIONFADEOUTTIME 163 | end 164 | local fAmount = fPower * fFrameDelta 165 | 166 | DoCurrentFrame(tGestureTable, tFrameData, iCurFrame, pl, fAmount, fFrameDelta, fPower, tGestureTable.Type == TYPE_POSTURE, tBuffer) 167 | if tGestureTable.DisplayCallback then 168 | tGestureTable.DisplayCallback(pl, sGestureName, tGestureTable, iCurFrame, tFrameData, fFrameDelta, fPower) 169 | end 170 | end 171 | 172 | for iBoneID, mMatrix in pairs(tBuffer) do 173 | pl:ManipulateBonePosition(iBoneID, mMatrix:GetTranslation()) 174 | pl:ManipulateBoneAngles(iBoneID, mMatrix:GetAngles()) 175 | end 176 | end 177 | 178 | local function ProcessAnimations(pl) 179 | pl:ResetBoneMatrix() 180 | 181 | local tLuaAnimations = pl.LuaAnimations 182 | for sGestureName, tGestureTable in pairs(tLuaAnimations) do 183 | local iCurFrame = tGestureTable.Frame 184 | local tFrameData = tGestureTable.FrameData[iCurFrame] 185 | local fFrameDelta = tGestureTable.FrameDelta 186 | local fDieTime = tGestureTable.DieTime 187 | local fPower = tGestureTable.Power 188 | if fDieTime and fDieTime - ANIMATIONFADEOUTTIME <= CurTime() then 189 | fPower = fPower * (fDieTime - CurTime()) / ANIMATIONFADEOUTTIME 190 | end 191 | local fAmount = fPower * fFrameDelta 192 | 193 | if fDieTime and fDieTime <= CurTime() then 194 | pl:StopLuaAnimation(sGestureName) 195 | elseif not tGestureTable.PreCallback or not tGestureTable.PreCallback(pl, sGestureName, tGestureTable, iCurFrame, tFrameData, fFrameDelta) then 196 | if tGestureTable.ShouldPlay and not tGestureTable.ShouldPlay(pl, sGestureName, tGestureTable, iCurFrame, tFrameData, fFrameDelta, fPower) then 197 | pl:StopLuaAnimation(sGestureName, 0.2) 198 | end 199 | 200 | if tGestureTable.Type == TYPE_GESTURE then 201 | if AdvanceFrame(tGestureTable, tFrameData) then 202 | pl:StopLuaAnimation(sGestureName) 203 | end 204 | elseif tGestureTable.Type == TYPE_POSTURE then 205 | if fFrameDelta < 1 and tGestureTable.TimeToArrive then 206 | fFrameDelta = math.min(1, fFrameDelta + FrameTime() * (1 / tGestureTable.TimeToArrive)) 207 | tGestureTable.FrameDelta = fFrameDelta 208 | end 209 | else 210 | AdvanceFrame(tGestureTable, tFrameData) 211 | end 212 | end 213 | end 214 | 215 | if pl.LuaAnimations then 216 | BuildBonePositions(pl) 217 | end 218 | end 219 | 220 | hook.Add("Think", "BoneAnimThink", function() 221 | for _, pl in pairs(player.GetAll()) do 222 | if pl.LuaAnimations and pl:IsValid() then 223 | ProcessAnimations(pl) 224 | end 225 | end 226 | end) 227 | 228 | hook.Add("CalcMainActivity", "LuaAnimationSequence", function(pl) 229 | if pl.InSequence then 230 | pl:ResetInSequence() 231 | return 0, 0 232 | end 233 | end) 234 | 235 | local meta = FindMetaTable("Entity") 236 | if not meta then return end 237 | 238 | function meta:ResetBoneMatrix() 239 | for i=0, self:GetBoneCount() - 1 do 240 | self:ManipulateBoneAngles(i, angle_zero) 241 | self:ManipulateBonePosition(i, vector_origin) 242 | end 243 | end 244 | 245 | function meta:ResetLuaAnimation(sAnimation, fDieTime, fPower, fTimeScale) 246 | local animtable = Animations[sAnimation] 247 | if animtable then 248 | self.LuaAnimations = self.LuaAnimations or {} 249 | 250 | self.LuaAnimations[sAnimation] = {Frame = animtable.StartFrame or 1, FrameDelta = animtable.Type == TYPE_POSTURE and not animtable.TimeToArrive and 1 or 0, FrameData = animtable.FrameData, 251 | TimeScale = fTimeScale or animtable.TimeScale or 1, Type = animtable.Type, RestartFrame = animtable.RestartFrame, TimeToArrive = animtable.TimeToArrive, Callback = animtable.Callback, 252 | ShouldPlay = animtable.ShouldPlay, PreCallback = animtable.PreCallback, Power = fPower or animtable.Power or 1, DieTime = fDieTime or animtable.DieTime, Group = animtable.Group, 253 | UseReferencePose = animtable.UseReferencePose, Interpolation = animtable.Interpolation} 254 | 255 | self:ResetLuaAnimationProperties() 256 | end 257 | end 258 | 259 | function meta:SetLuaAnimation(sAnimation, fDieTime, fPower, fTimeScale) 260 | if self.LuaAnimations and self.LuaAnimations[sAnimation] then return end 261 | 262 | self:ResetLuaAnimation(sAnimation, fDieTime, fPower, fTimeScale) 263 | end 264 | 265 | function meta:SetLuaAnimationPower(sAnimation, fPower) 266 | if self.LuaAnimations and self.LuaAnimations[sAnimation] then 267 | self.LuaAnimations[sAnimation].Power = fPower 268 | end 269 | end 270 | 271 | function meta:SetLuaAnimationTimeScale(sAnimation, fTimeScale) 272 | if self.LuaAnimations and self.LuaAnimations[sAnimation] then 273 | self.LuaAnimations[sAnimation].TimeScale = fTimeScale 274 | end 275 | end 276 | 277 | function meta:SetLuaAnimationDieTime(sAnimation, fTime) 278 | if self.LuaAnimations and self.LuaAnimations[sAnimation] then 279 | if self.LuaAnimations[sAnimation].DieTime then 280 | self.LuaAnimations[sAnimation].DieTime = math.min(self.LuaAnimations[sAnimation].DieTime, fTime) 281 | else 282 | self.LuaAnimations[sAnimation].DieTime = fTime 283 | end 284 | end 285 | end 286 | 287 | function meta:ResetInSequence() 288 | local anims = self.LuaAnimations 289 | if anims then 290 | for sAnimation, tAnimTab in pairs(anims) do 291 | if tAnimTab.Type == TYPE_SEQUENCE and (not tAnimTab.DieTime or CurTime() < tAnimTab.DieTime - ANIMATIONFADEOUTTIME) or tAnimTab.UseReferencePose then 292 | self.InSequence = true 293 | return 294 | end 295 | end 296 | 297 | self.InSequence = nil 298 | end 299 | end 300 | 301 | function meta:ResetLuaAnimationProperties() 302 | local anims = self.LuaAnimations 303 | if anims and table.Count(anims) > 0 then 304 | self:SetIK(false) 305 | self:ResetInSequence() 306 | else 307 | --self:SetIK(true) 308 | self.LuaAnimations = nil 309 | self.InSequence = nil 310 | self:ResetBoneMatrix() 311 | end 312 | end 313 | 314 | -- Time is optional, sets the die time to CurTime() + fTime 315 | function meta:StopLuaAnimation(sAnimation, fTime) 316 | local anims = self.LuaAnimations 317 | if anims and anims[sAnimation] then 318 | if fTime then 319 | if anims[sAnimation].DieTime then 320 | anims[sAnimation].DieTime = math.min(anims[sAnimation].DieTime, CurTime() + fTime) 321 | else 322 | anims[sAnimation].DieTime = CurTime() + fTime 323 | end 324 | else 325 | anims[sAnimation] = nil 326 | end 327 | 328 | self:ResetLuaAnimationProperties() 329 | end 330 | end 331 | 332 | function meta:StopLuaAnimationGroup(sGroup, fTime) 333 | if self.LuaAnimations then 334 | for animname, animtable in pairs(self.LuaAnimations) do 335 | if animtable.Group == sGroup then 336 | self:StopLuaAnimation(animname, fTime) 337 | end 338 | end 339 | end 340 | end 341 | 342 | function meta:StopAllLuaAnimations(fTime) 343 | if self.LuaAnimations then 344 | for name in pairs(self.LuaAnimations) do 345 | self:StopLuaAnimation(name, fTime) 346 | end 347 | end 348 | end 349 | -------------------------------------------------------------------------------- /mocap_controller.lua: -------------------------------------------------------------------------------- 1 | local ENT = {} 2 | 3 | ENT.Type = "anim" 4 | 5 | function ENT:Initialize() 6 | self:DrawShadow(false) 7 | self:SetMoveType(MOVETYPE_NONE) 8 | self:SetSolid(SOLID_NONE) 9 | 10 | if SERVER then 11 | self:SetStartTime(CurTime()) 12 | end 13 | end 14 | 15 | function ENT:OnRemove() 16 | if SERVER then 17 | local parent = self:GetParent() 18 | if parent:IsValid() and parent:Alive() then 19 | local rag = parent:GetRagdollEntity() 20 | if IsValid(rag) then rag:Remove() end 21 | end 22 | end 23 | end 24 | 25 | function ENT:Think() 26 | if SERVER then return end 27 | 28 | local animdata = self:GetAnimationData() 29 | if not animdata then return end 30 | local parent = self:GetParent() 31 | if not parent:IsValid() then return end 32 | local rag = parent:GetRagdollEntity() 33 | if not IsValid(rag) then return end 34 | 35 | 36 | local delta = CurTime() - self:GetStartTime() 37 | local frame = math.Clamp(delta, 1, animdata.NumFrames) 38 | 39 | for iBoneID, tBoneInfo in pairs(animdata.FrameData) do 40 | local tFrameData = tBoneInfo[frame] 41 | if not tFrameData then continue end 42 | 43 | if type(iBoneID) ~= "number" then 44 | iBoneID = rag:LookupBone(iBoneID) 45 | end 46 | if not iBoneID then continue end 47 | 48 | local iPhysID = rag:TranslateBoneToPhysBone(iBoneID) 49 | if not iPhysID then continue end 50 | 51 | local phys = rag:GetPhysicsObjectNum(iPhysID) 52 | if IsValid(phys) then 53 | phys:SetPos(parent:LocalToWorld(tFrameData[1])) 54 | phys:SetAngles(parent:LocalToWorldAngles(tFrameData[2])) 55 | end 56 | end 57 | 58 | 59 | self:NextThink(CurTime()) 60 | self:SetNextClientThink(CurTime()) 61 | return true 62 | end 63 | 64 | function ENT:SetStartTime(m) 65 | self:SetDTFloat(0, m) 66 | end 67 | 68 | function ENT:GetStartTime() 69 | return self:GetDTFloat(0) 70 | end 71 | 72 | function ENT:SetAnimationName(m) 73 | self:SetDTString(0, m) 74 | end 75 | 76 | function ENT:GetAnimationName() 77 | return self:GetDTString(0) 78 | end 79 | 80 | function ENT:GetAnimationData() 81 | return GetMocapAnimations()[self:GetAnimationName()] 82 | end 83 | 84 | if CLIENT then 85 | function ENT:Draw() 86 | end 87 | end 88 | 89 | scripted_ents.Register(ENT, "mocap_controller") 90 | -------------------------------------------------------------------------------- /sh_boneanimlib.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | Bone Animations Library 4 | Created by William "JetBoom" Moodhe (williammoodhe@gmail.com / www.noxiousnet.com) 5 | Because I wanted custom, dynamic animations. 6 | Give credit or reference if used in your creations. 7 | 8 | ]] 9 | 10 | if GetLuaAnimations ~= nil then return end 11 | 12 | TYPE_GESTURE = 0 -- Gestures are keyframed animations that use the current position and angles of the bones. They play once and then stop automatically. 13 | TYPE_POSTURE = 1 -- Postures are static animations that use the current position and angles of the bones. They stay that way until manually stopped. Use TimeToArrive if you want to have a posture lerp. 14 | TYPE_STANCE = 2 -- Stances are keyframed animations that use the current position and angles of the bones. They play forever until manually stopped. Use RestartFrame to specify a frame to go to if the animation ends (instead of frame 1). 15 | TYPE_SEQUENCE = 3 -- Sequences are keyframed animations that use the reference pose. They play forever until manually stopped. Use RestartFrame to specify a frame to go to if the animation ends (instead of frame 1). 16 | -- You can also use StartFrame to specify a starting frame for the first loop. 17 | 18 | INTERP_LINEAR = 0 -- Straight linear interp. 19 | INTERP_COSINE = 1 -- Best compatability / quality balance. 20 | INTERP_CUBIC = 2 -- Overall best quality blending but may cause animation frames to go 'over the top'. 21 | INTERP_DEFAULT = INTERP_COSINE 22 | 23 | local Animations = {} 24 | 25 | function GetLuaAnimations() 26 | return Animations 27 | end 28 | 29 | function RegisterLuaAnimation(sName, tInfo) 30 | if tInfo.FrameData then 31 | local BonesUsed = {} 32 | for iFrame, tFrame in ipairs(tInfo.FrameData) do 33 | for iBoneID, tBoneTable in pairs(tFrame.BoneInfo) do 34 | BonesUsed[iBoneID] = (BonesUsed[iBoneID] or 0) + 1 35 | tBoneTable.MU = tBoneTable.MU or 0 36 | tBoneTable.MF = tBoneTable.MF or 0 37 | tBoneTable.MR = tBoneTable.MR or 0 38 | tBoneTable.RU = tBoneTable.RU or 0 39 | tBoneTable.RF = tBoneTable.RF or 0 40 | tBoneTable.RR = tBoneTable.RR or 0 41 | end 42 | end 43 | 44 | if #tInfo.FrameData > 1 then 45 | for iBoneUsed, iTimesUsed in pairs(BonesUsed) do 46 | for iFrame, tFrame in ipairs(tInfo.FrameData) do 47 | if not tFrame.BoneInfo[iBoneUsed] then 48 | tFrame.BoneInfo[iBoneUsed] = {MU = 0, MF = 0, MR = 0, RU = 0, RF = 0, RR = 0} 49 | end 50 | end 51 | end 52 | end 53 | end 54 | Animations[sName] = tInfo 55 | end 56 | 57 | ----------------------------- 58 | -- Deserialize / Serialize -- 59 | ----------------------------- 60 | local sandbox_env = {Vector = Vector, Angle = Angle} 61 | 62 | function Deserialize(sIn) 63 | local out = {} 64 | 65 | if #sIn == 0 or string.sub(sIn, -1) ~= "}" then return out end 66 | 67 | if string.sub(sIn, 1, 4) ~= "SRL=" then sIn = "SRL="..sIn end 68 | 69 | if string.sub(sIn, 5, 5) ~= "{" then return out end 70 | 71 | sIn = sIn.." return SRL" 72 | local func = CompileString(sIn, "deserialize", false) 73 | if type(func) == "string" then 74 | print("Deserialization error: "..func) 75 | else 76 | setfenv(func, sandbox_env) 77 | out = func() or out 78 | end 79 | 80 | return out 81 | end 82 | 83 | local allowedtypes = {} 84 | allowedtypes["string"] = true 85 | allowedtypes["number"] = true 86 | allowedtypes["table"] = true 87 | allowedtypes["Vector"] = true 88 | allowedtypes["Angle"] = true 89 | allowedtypes["boolean"] = true 90 | local function MakeTable(tab, done) 91 | local str = "" 92 | local done = done or {} 93 | 94 | local sequential = table.IsSequential(tab) 95 | 96 | for key, value in pairs(tab) do 97 | local keytype = type(key) 98 | local valuetype = type(value) 99 | 100 | if allowedtypes[keytype] and allowedtypes[valuetype] then 101 | if sequential then 102 | key = "" 103 | else 104 | if keytype == "number" or keytype == "boolean" then 105 | key ="["..tostring(key).."]=" 106 | else 107 | key = "["..string.format("%q", tostring(key)).."]=" 108 | end 109 | end 110 | 111 | if valuetype == "table" and not done[value] then 112 | done[value] = true 113 | if type(value._serialize) == "function" then 114 | str = str..key..value:_serialize().."," 115 | else 116 | str = str..key.."{"..MakeTable(value, done).."}," 117 | end 118 | else 119 | if valuetype == "string" then 120 | value = string.format("%q", value) 121 | elseif valuetype == "Vector" then 122 | value = "Vector("..value.x..","..value.y..","..value.z..")" 123 | elseif valuetype == "Angle" then 124 | value = "Angle("..value.pitch..","..value.yaw..","..value.roll..")" 125 | else 126 | value = tostring(value) 127 | end 128 | 129 | str = str .. key .. value .. "," 130 | end 131 | end 132 | end 133 | 134 | if string.sub(str, -1) == "," then 135 | return string.sub(str, 1, #str - 1) 136 | else 137 | return str 138 | end 139 | end 140 | 141 | function Serialize(tIn, bRaw) 142 | if #tIn == 0 then 143 | local empty = true 144 | for k in pairs(tIn) do 145 | empty = false 146 | break 147 | end 148 | if empty then 149 | return "" 150 | end 151 | end 152 | 153 | if bRaw then 154 | return "{"..MakeTable(tIn).."}" 155 | end 156 | 157 | return "SRL={"..MakeTable(tIn).."}" 158 | end 159 | --------------------------------- 160 | -- End Deserialize / Serialize -- 161 | --------------------------------- 162 | 163 | /* EXAMPLES! 164 | 165 | -- If your animation is only used on one model, use numbers instead of bone names (cache the lookup). 166 | -- If it's being used on a wide array of models (including default player models) then you should use bone names. 167 | -- You can use Callback as a function instead of MU, RR, etc. which will allow you to do some interesting things. 168 | -- See cl_boneanimlib.lua for the full format. 169 | 170 | STANCE: stancetest 171 | A simple looping stance that stretches the model's spine up and down until stopped. 172 | 173 | RegisterLuaAnimation("stancetest", { 174 | FrameData = { 175 | { 176 | BoneInfo = { 177 | ["ValveBiped.Bip01_Spine"] = { 178 | MU = 64 179 | } 180 | }, 181 | FrameRate = 0.25 182 | }, 183 | { 184 | BoneInfo = { 185 | ["ValveBiped.Bip01_Spine"] = { 186 | MU = -32 187 | } 188 | }, 189 | FrameRate = 1.5 190 | }, 191 | { 192 | BoneInfo = { 193 | ["ValveBiped.Bip01_Spine"] = { 194 | MU = 32 195 | } 196 | }, 197 | FrameRate = 4 198 | } 199 | }, 200 | RestartFrame = 2, 201 | Type = TYPE_STANCE 202 | }) 203 | 204 | --[[ 205 | STANCE: staffholdspell 206 | To be used with the ACT_HL2MP_IDLE_MELEE2 animation. 207 | Player holds the staff so that their left hand is over the top of it. 208 | ]] 209 | 210 | RegisterLuaAnimation("staffholdspell", { 211 | FrameData = { 212 | { 213 | BoneInfo = { 214 | ["ValveBiped.Bip01_R_Forearm"] = { 215 | RU = 40, 216 | RF = -40 217 | }, 218 | ["ValveBiped.Bip01_R_Upperarm"] = { 219 | RU = 40 220 | }, 221 | ["ValveBiped.Bip01_R_Hand"] = { 222 | RU = -40 223 | }, 224 | ["ValveBiped.Bip01_L_Forearm"] = { 225 | RU = 40 226 | }, 227 | ["ValveBiped.Bip01_L_Hand"] = { 228 | RU = -40 229 | } 230 | }, 231 | FrameRate = 6 232 | }, 233 | { 234 | BoneInfo = { 235 | ["ValveBiped.Bip01_R_Forearm"] = { 236 | RU = 2, 237 | }, 238 | ["ValveBiped.Bip01_R_Upperarm"] = { 239 | RU = 1 240 | }, 241 | ["ValveBiped.Bip01_R_Hand"] = { 242 | RU = -10 243 | }, 244 | ["ValveBiped.Bip01_L_Forearm"] = { 245 | RU = 8 246 | }, 247 | ["ValveBiped.Bip01_L_Hand"] = { 248 | RU = -12 249 | } 250 | }, 251 | FrameRate = 0.4 252 | }, 253 | { 254 | BoneInfo = { 255 | ["ValveBiped.Bip01_R_Forearm"] = { 256 | RU = -2, 257 | }, 258 | ["ValveBiped.Bip01_R_Upperarm"] = { 259 | RU = -1 260 | }, 261 | ["ValveBiped.Bip01_R_Hand"] = { 262 | RU = 10 263 | }, 264 | ["ValveBiped.Bip01_L_Forearm"] = { 265 | RU = -8 266 | }, 267 | ["ValveBiped.Bip01_L_Hand"] = { 268 | RU = 12 269 | } 270 | }, 271 | FrameRate = 0.1 272 | } 273 | }, 274 | RestartFrame = 2, 275 | Type = TYPE_STANCE, 276 | ShouldPlay = function(pl, sGestureName, tGestureTable, iCurFrame, tFrameData) 277 | local wepstatus = pl.WeaponStatus 278 | return wepstatus and wepstatus:IsValid() and wepstatus:GetSkin() == 1 and wepstatus.IsStaff 279 | end 280 | }) 281 | 282 | */ 283 | -------------------------------------------------------------------------------- /sh_mocap.lua: -------------------------------------------------------------------------------- 1 | --Mocap 2 | 3 | include("mocap_controller.lua") 4 | 5 | local Animations = {} 6 | 7 | function GetMocapAnimations() 8 | return Animations 9 | end 10 | 11 | function RegisterMocapAnimation(sName, tInfo) 12 | local numframes = 0 13 | 14 | for bonename, bonedata in pairs(tInfo.FrameData) do 15 | numframes = math.max(numframes, #bonedata) 16 | end 17 | 18 | tInfo.NumFrames = numframes 19 | Animations[sName] = tInfo 20 | end 21 | 22 | RegisterMocapAnimation("mocaptest", { 23 | FrameData = { 24 | ["ValveBiped.Bip01_Spine"] = { 25 | { 26 | Vector(0, 0, 30), 27 | Angle(0, 0, 0) 28 | }, 29 | { 30 | Vector(0, 0, 40), 31 | Angle(0, 0, 0) 32 | }, 33 | { 34 | Vector(0, 0, 50), 35 | Angle(0, 0, 0) 36 | } 37 | } 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /sv_mocap.lua: -------------------------------------------------------------------------------- 1 | if CLIENT or GetLuaAnimations ~= nil then return end 2 | 3 | include("sh_boneanimlib.lua") 4 | AddCSLuaFile("cl_boneanimlib.lua") 5 | AddCSLuaFile("sh_boneanimlib.lua") 6 | AddCSLuaFile("cl_animeditor.lua") 7 | 8 | hook.Add("Initialize", "BAL_Initialize", function() 9 | util.AddNetworkString("bal_reset") 10 | util.AddNetworkString("bal_set") 11 | util.AddNetworkString("bal_stop") 12 | util.AddNetworkString("bal_stopgroup") 13 | util.AddNetworkString("bal_stopall") 14 | end) 15 | 16 | -- These are unreliable. All Lua animations should be set on both the client and server (predicted). 17 | local meta = FindMetaTable("Entity") 18 | if not meta then return end 19 | 20 | local function ResetMoCap(pl, sAnimation) 21 | local anim = GetLuaAnimations()[sAnimation] 22 | if anim and anim.Type == TYPE_MOCAP then 23 | pl:CreateRagdoll() 24 | end 25 | end 26 | 27 | function meta:ResetLuaAnimation(sAnimation, fTime, fPower, fTimeScale) 28 | net.Start("bal_reset") 29 | net.WriteEntity(self) 30 | net.WriteString(sAnimation) 31 | net.WriteFloat(fTime or -1) 32 | net.WriteFloat(fPower or -1) 33 | net.WriteFloat(fTimeScale or -1) 34 | net.Broadcast() 35 | 36 | ResetMoCap(self, sAnimation) 37 | end 38 | 39 | function meta:SetLuaAnimation(sAnimation, fTime, fPower, fTimeScale) 40 | net.Start("bal_set") 41 | net.WriteEntity(self) 42 | net.WriteString(sAnimation) 43 | net.WriteFloat(fTime or -1) 44 | net.WriteFloat(fPower or -1) 45 | net.WriteFloat(fTimeScale or -1) 46 | net.Broadcast() 47 | 48 | ResetMoCap(self, sAnimation) 49 | end 50 | 51 | function meta:StopLuaAnimation(sAnimation, fTime) 52 | net.Start("bal_stop") 53 | net.WriteEntity(self) 54 | net.WriteString(sAnimation) 55 | net.WriteFloat(fTime or 0) 56 | net.Broadcast() 57 | end 58 | 59 | function meta:StopLuaAnimationGroup(sAnimation, fTime) 60 | net.Start("bal_stopgroup") 61 | net.WriteEntity(self) 62 | net.WriteString(sAnimation) 63 | net.WriteFloat(fTime or 0) 64 | net.Broadcast() 65 | end 66 | 67 | function meta:StopAllLuaAnimations(fTime) 68 | net.Start("bal_stopall") 69 | net.WriteEntity(self) 70 | net.WriteFloat(fTime or 0) 71 | net.Broadcast() 72 | end 73 | --------------------------------------------------------------------------------