home *** CD-ROM | disk | FTP | other *** search
/ Unreal Tools / UnrealTools.iso / ActorXImporter.zip / ActorXImporter.ms next >
Text File  |  2017-12-06  |  62KB  |  2,262 lines

  1. /*
  2.  
  3.  ActorX mesh (psk) and animation (psa) importer for 3ds Max
  4.  
  5.  Created:    September 18 2009
  6.  
  7.  Author:    Konstantin Nosov (aka Gildor)
  8.  
  9.  Web page:    http://www.gildor.org/projects/unactorx
  10.  
  11.  Revision History:
  12.  
  13.     06.12.2017 v1.35
  14.     - an attempt to make smoothing groups working
  15.     - renamed "recurse" option to "look in subfolders" to be less confising to new users
  16.  
  17.     16.10.2017 v1.34
  18.     - added possibility to select and open multiple psk files at time
  19.  
  20.     21.07.2015 v1.33
  21.     - saving bind pose information inside bone objects, this information will survive saving scene
  22.       to a max file
  23.  
  24.     18.07.2015 v1.32
  25.     - allowing psa import to work with any mesh, i.e. without previously imported psk file
  26.  
  27.     14.07.2015 v1.31
  28.     - trying to detect and repair bad vertex weights for imported psk file
  29.  
  30.     01.02.2015 v1.30
  31.     - added animation import option "import at slider position"
  32.     - integrated patch by Ayrshi (http://www.gildor.org/smf/index.php/topic,1925.0.html) intended to
  33.       improve mesh normals
  34.  
  35.     02.12.2014 v1.29
  36.     - ActorX Imported now could be bound to toolbar, keyboard or menu - use "Customize user interface",
  37.       category "Gildor Tools", and then use "ActorX Importer" as you like
  38.     - reordered controls, separated some options to own rollouts for easy reordering etc
  39.     - preserving dialog position, scroll position and rollout "open" state during 3ds Max session (until
  40.       Max closed)
  41.  
  42.     28.11.2014 v1.28
  43.     - added "mesh translation" options to settings
  44.     - "advanced settings" are not stored to the ini file anymore
  45.  
  46.     16.12.2013 v1.27
  47.     - added option "Don't conjugate root bone"
  48.  
  49.     10.06.2012 v1.26
  50.     - stability improvements
  51.       more info: MaxScript documentation, "Do not use return, break, exit or continue"
  52.  
  53.     02.06.2012 v1.25
  54.     - fixed Max 2013 support; fix made by sunnydavis, check
  55.       http://www.gildor.org/smf/index.php/topic,1408.0.html for details
  56.  
  57.     18.02.2012 v1.24
  58.     - fixed parsing psa config file with spaces in track names
  59.  
  60.     07.02.2012 v1.23
  61.     - support for extra UV sets stored in standard psk format (ActorX 2010)
  62.  
  63.     23.01.2012 v1.22
  64.     - fixed automatic loading of DDS textures for materials
  65.  
  66.     06.12.2011 v1.21
  67.     - fixed "translation mode" checkbox to work with psa without config file
  68.  
  69.     01.12.2011 v1.20
  70.     - implemented loading of DDS textures
  71.  
  72.     26.11.2011 v1.19
  73.     - implemented support for loading pskx files with more than 64k vertices
  74.     - added option to control behaviour of animation with rotation-only tracks: you can let AnimSet
  75.       to decide which bones will use animated translation, you can force to use translation from the
  76.       animation (old, pre-1.18 behaviour) or force to not use animated translation at all; the option
  77.       is located in "Animation import" group
  78.  
  79.     09.11.2011 v1.18
  80.     - implemented support for animation tracks without translation keys
  81.     - reading extended psa information from the .config file, removed psax ANIMFLAGS section support
  82.  
  83.     06.11.2011 v1.17
  84.     - eliminated error messages when loading psk or psa file with unknown section name (SCALEKEYS etc)
  85.     - implemented support for pskx with 2 or more UV channels
  86.  
  87.     03.05.2011 v1.16
  88.     - improved animation cleanup
  89.  
  90.     01.01.2011 v1.15
  91.     - workaround for loading animation with the root bone name different than mesh root bone
  92.     - removed "Load confirmation" setting (not needed anymore because of functional "batch export")
  93.  
  94.     29.12.2010 v1.14
  95.     - added "Batch export" tool
  96.  
  97.     22.12.2010 v1.13
  98.     - mesh rotation formula is now identical to used in UnrealEd
  99.     - added "Clear scene" tool
  100.  
  101.     15.12.2010 v1.12
  102.     - added mesh rotation settings
  103.     - added protection from errors appeared when updating this script while 3ds Max is running
  104.  
  105.     09.09.2010 v1.11
  106.     - added "reorient bones" option
  107.  
  108.     23.07.2010 v1.10
  109.     - implemented extended ActorX format (pskx and psax) support
  110.     - "tools" rollout with options to restore mesh bindpose and remove animations
  111.  
  112.     24.04.2010 v1.09
  113.     - applying normalmap using correct technique (previously was a bumpmap)
  114.  
  115.     14.04.2010 v1.08
  116.     - fixed loading of psk files with root bone parent set to -1 (usually it is 0)
  117.  
  118.     20.02.2010 v1.07
  119.     - added "Load confirmation" setting to display message box after completion of operation
  120.     - added "Reposition existing bones" option
  121.     - fixed error when loading .mat files with missing textures
  122.  
  123.     12.12.2009 v1.06
  124.     - fixed merging meshes on a single skeleton when previously loaded mesh is not in bind
  125.       pose
  126.     - improved compatibility with Epic's ActorX Exporter (dropping trailing spaces from
  127.       bone names)
  128.  
  129.     18.09.2009 v1.05
  130.     - implemented materal loading
  131.     - fixing duplicate bone names
  132.  
  133.     29.09.2009 v1.04
  134.     - implemented support for loading non-skeletal (static) meshes
  135.  
  136.     26.09.2009 v1.03
  137.     - fixed bug with interpolation between first two animation keyframes
  138.     - option to fix animation looping (duplicate first animation frame after last frame)
  139.     - added button to load all animations from psa file
  140.     - progress bar for loading animation with "cancel" capabilities
  141.     - option to not load mesh skin (load skeleton only)
  142.     - storing last used directory separately for psk and psa
  143.  
  144.     25.09.2009 v1.02
  145.     - added option to scale mesh and animations when loading
  146.     - added options for texture search (path, recursive search)
  147.     - added option to ask for missing texture files when mesh is loading
  148.  
  149.     24.09.2009 v1.01
  150.     - fixed bug in a vertex weighting code
  151.     - saving settings to ActorXImporter.ini (Max 9 and higher)
  152.     - saving last used psk/psa directory
  153.     - settings to change bone size for a new mesh
  154.  
  155.     22.09.2009 v1.00
  156.     - first public release
  157.  
  158. */
  159.  
  160.  
  161. /*
  162. TODO:
  163. - option to create separate materials, not submaterials
  164. - do not create material when it is already exists - but how to find whether I need to get loaded material
  165.   or create a new one?
  166. */
  167.  
  168. /*
  169. NOTES:
  170. - setBoneEnable false 0:
  171.   This call is required. Without it, we will have numerous problems with imported skeleton. Note that FBX
  172.   importer will enable "bones" mode, however we can't use it in Max. Why "bone" mode could be useful: hiding
  173.   bones could not hide the skeleton when bones are off, and works well when they are on. Why "bone" mode should
  174.   be disabled - otherwise we've got problems with rotation/moving of particular bones, they behave like
  175.   connected objects. Also saw a bug with imported animation with "force AnimSet translation" - mesh could became
  176.   bronen in parts, but began to behave well when unlinked child bone of bad bone and relinked it back. So it's
  177.   easier to disable bone mode than to fight against all the bugs.
  178. */
  179.  
  180. -- constant used to detect ActorX Importer updates during single 3ds Max session
  181. global AX_IMPORTER_VERSION = 135
  182.  
  183. -------------------------------------------------------------------------------
  184. --    Global variables
  185. -------------------------------------------------------------------------------
  186.  
  187. global g_seeThru
  188. global g_skelOnly
  189. global g_updateTime
  190. global g_playAnim
  191. global g_animAtSlider
  192. global g_animTransMode            -- 1 = from AnimSet, 2 = force mesh translation, 3 = force AnimSet translation
  193. global g_fixLooping
  194. global g_lastDir1
  195. global g_lastDir2
  196. global g_texDir
  197. global g_texRecurse
  198. global g_texMissAction
  199. global g_boneSize
  200. global g_reposBones
  201. global g_rotY
  202. global g_rotP
  203. global g_rotR
  204. global g_transX
  205. global g_transY
  206. global g_transZ
  207. global g_meshScale
  208. global g_reorientBones
  209. global g_dontConjugateRoot
  210. global Anims                    -- array of AnimInfoBinary
  211.  
  212.  
  213. -------------------------------------------------------------------------------
  214. --    Default settings
  215. -------------------------------------------------------------------------------
  216.  
  217. fn axDefaultSettings =
  218. (
  219.     -- defaults settings
  220.     g_seeThru    = false
  221.     g_skelOnly   = false
  222.     g_updateTime = true
  223.     g_playAnim   = false
  224.     g_animAtSlider = false
  225.     g_animTransMode = 1
  226.     g_fixLooping = false
  227.     g_lastDir1   = ""
  228.     g_lastDir2   = ""
  229.     g_texDir     = ""
  230.     g_texRecurse = true
  231.     g_texMissAction = 1
  232.     g_boneSize   = 0.5
  233.     g_reposBones = true
  234.     g_rotY       = 0
  235.     g_rotP       = 0
  236.     g_rotR       = 0
  237.     g_transX     = 0
  238.     g_transY     = 0
  239.     g_transZ     = 0
  240.     g_meshScale  = 1.0
  241.     g_reorientBones = false
  242.     g_dontConjugateRoot = false
  243. )
  244.  
  245.  
  246. -------------------------------------------------------------------------------
  247. --    Configuration
  248. -------------------------------------------------------------------------------
  249.  
  250. configFile = undefined
  251. if getSourceFileName != undefined then    -- checking Max version (Max9+) ...
  252. (
  253.     local s = getSourceFileName()
  254.     configFile = (getFilenamePath s) + (getFilenameFile s) + ".ini"
  255. )
  256. else
  257. (
  258.     -- workaround for Max 8 and older
  259.     configFile = (getDir #scripts) + "\ActorXImporter.ini"
  260. )
  261.  
  262.  
  263. tmp_v = undefined        -- global variable, helper for axDoSetting() (required for execute() ...)
  264. g_isLoading = true        -- axDoSetting() mode
  265.  
  266. fn axDoSetting name var =
  267. (
  268.     local default = execute var                            -- value has the same type as var
  269.     if g_isLoading then
  270.     (
  271.         try
  272.         (
  273.             -- loading value
  274.             tmp_v = getINISetting configFile "Main" name    -- get from ini as string
  275.             if (tmp_v != "") and (tmp_v != "undefined") then
  276.             (
  277.                 local type = classOf default
  278. --                format "reading % (%) = %\n" var type tmp_v
  279.                 if (not isKindOf default String) then
  280.                     execute (var + "=tmp_v as " + (type as string))
  281.                 else
  282.                     execute (var + "=tmp_v")                -- no conversion
  283.             )
  284.         )
  285.         catch
  286.         (
  287.             format "Reading %: %\n" name (getCurrentException())
  288.         )
  289.     )
  290.     else
  291.     (
  292.         -- saving value
  293.         setINISetting configFile "Main" name (default as string)
  294.     )
  295. )
  296.  
  297.  
  298. fn axSerializeSettings isLoading =
  299. (
  300.     if configFile == undefined then return undefined            -- could happen with old 3ds Max, where getSourceFileName() doesn't exist
  301.     if isLoading then
  302.     (
  303.         if not doesFileExist configFile then return undefined    -- no config file
  304.     )
  305.     g_isLoading = isLoading
  306.     -- read/write settings
  307.     axDoSetting "LastUsedDir"   "g_lastDir1"
  308.     axDoSetting "LastUsedDir2"  "g_lastDir2"
  309.     axDoSetting "TexturesDir"   "g_texDir"
  310.     axDoSetting "TexRecurse"    "g_texRecurse"
  311.     axDoSetting "TexMissAction" "g_texMissAction"
  312.     axDoSetting "AutoPlayAnim"  "g_playAnim"
  313.     axDoSetting "AnimAtSlider"  "g_animAtSlider"
  314.     axDoSetting "AnimTransMode" "g_animTransMode"
  315.     axDoSetting "UpdateTime"    "g_updateTime"
  316.     axDoSetting "FixLoopAnim"   "g_fixLooping"
  317.     axDoSetting "SeeThru"       "g_seeThru"
  318.     axDoSetting "SkelOnly"      "g_skelOnly"
  319.     axDoSetting "BoneSize"      "g_boneSize"
  320.     axDoSetting "ReposBones"    "g_reposBones"
  321.     axDoSetting "MeshYaw"       "g_rotY"
  322.     axDoSetting "MeshPitch"     "g_rotP"
  323.     axDoSetting "MeshRoll"      "g_rotR"
  324.     axDoSetting "MeshX"         "g_transX"
  325.     axDoSetting "MeshY"         "g_transY"
  326.     axDoSetting "MeshZ"         "g_transZ"
  327.     axDoSetting "MeshScale"     "g_meshScale"
  328. --    axDoSetting "ReorientBones" "g_reorientBones"
  329. --    axDoSetting "DontConjRoot"  "g_dontConjugateRoot"
  330. )
  331.  
  332.  
  333. -------------------------------------------------------------------------------
  334. --    Service functions
  335. -------------------------------------------------------------------------------
  336.  
  337. fn ErrorMessage text =
  338. (
  339.     local msg = ("ERROR: " + text + "\n")
  340.     format "%\n" msg
  341.     messageBox msg
  342.     throw msg
  343. )
  344.  
  345.  
  346. fn TrimSpaces text =
  347. (
  348.     trimLeft(trimRight(text))
  349. )
  350.  
  351.  
  352. fn IsEndOfFile bstream =
  353. (
  354.     local savePos = ftell bstream
  355.     fseek bstream 0 #seek_end            -- compute file size
  356.     local fileSize = ftell bstream
  357.     fseek bstream savePos #seek_set
  358.     (savePos >= fileSize)
  359. )
  360.  
  361.  
  362. fn ReadFixedString bstream fixedLen =
  363. (
  364.     local str = ""
  365.     local length = 0
  366.     local finished = false
  367.     for i = 1 to fixedLen do
  368.     (
  369.         local c = ReadByte bstream #unsigned
  370.         if c == 0 then finished = true    -- end of line char
  371.         if not finished then             -- has end of line before - skip remaining chars
  372.         (
  373.             -- not "finished" string
  374.             str += bit.intAsChar(c)        -- append a character
  375.             if c != 32 then length = i    -- position of last non-space char
  376.         )
  377.     )
  378.     substring str 1 length                -- return first "length" chars
  379. )
  380.  
  381. fn ReadVector2 bstream =
  382. (
  383.     local v = point2 0 0
  384.     v.x = ReadFloat bstream
  385.     v.y = ReadFloat bstream
  386.     v
  387. )
  388.  
  389. fn ReadFVector bstream =
  390. (
  391.     local v = point3 0 0 0
  392.     v.x = ReadFloat bstream
  393.     v.y = ReadFloat bstream
  394.     v.z = ReadFloat bstream
  395.     v
  396. )
  397.  
  398. fn ReadFQuat bstream =
  399. (
  400.     local q = quat 0 0 0 0
  401.     q.x = ReadFloat bstream
  402.     q.y = ReadFloat bstream
  403.     q.z = ReadFloat bstream
  404.     q.w = ReadFloat bstream
  405.     q
  406. )
  407.  
  408. -- Function used to determine bone length
  409. fn axFindFirstChild boneArray boneIndex =
  410. (
  411.     local res = undefined, notfound = true
  412.     for i = 1 to boneArray.count while notfound do
  413.     (
  414.         if (i != boneIndex) then
  415.         (
  416.             bn = boneArray[i]
  417.             if bn.ParentIndex == boneIndex-1 then
  418.             (
  419.                 res = bn
  420.                 notfound = false
  421.             )
  422.         )
  423.     )
  424.     res
  425. )
  426.  
  427.  
  428. fn axFixBoneNames boneArray =
  429. (
  430.     -- Find and correct duplicate names
  431.     for i = 1 to (boneArray.count-1) do
  432.     (
  433.         local n = boneArray[i].Name
  434.         local dupCount = 1
  435.         for j = (i+1) to boneArray.count do
  436.         (
  437.             local n2 = boneArray[j].Name
  438.             if n == n2 then
  439.             (
  440.                 dupCount += 1
  441.                 n2 = n + "_" + (dupCount as string)
  442.                 format "Duplicate bone name \"%\", renamed to \"%\"\n" n n2
  443.                 boneArray[j].Name = n2
  444.             )
  445.         )
  446.     )
  447. )
  448.  
  449.  
  450. fn axFindFile path filename recurse:false =
  451. (
  452.     local res = undefined
  453.     local check = path + "\\" + filename
  454.     if doesFileExist check then
  455.     (
  456.         res = check
  457.     )
  458.     else if recurse then
  459.     (
  460.         local dirs = getDirectories (path + "/*")
  461.         local notfound = true
  462.         for dir in dirs while notfound do
  463.         (
  464.             res = axFindFile dir filename recurse:true
  465.             if res != undefined then
  466.             (
  467.                 notfound = false        -- break the loop
  468.             )
  469.         )
  470.     )
  471.     res
  472. )
  473.  
  474.  
  475. fn axGetRootMatrix =
  476. (
  477.     local angles = eulerAngles g_rotR -g_rotP -g_rotY
  478.     local m = angles as matrix3
  479.     m.translation = [g_transX, g_transY, g_transZ]
  480.     m
  481. )
  482.  
  483. -- Reference: https://forums.autodesk.com/t5/3ds-max-programming/getopenfilename-for-multiple-files/td-p/4097903
  484. fn getMultiOpenFilenames caption: "Open" filename: "" types: "All Files (*.*)|*.*" default: 1 =
  485. (
  486.     local dlg = DotNetObject "System.Windows.Forms.OpenFileDialog"
  487.     dlg.multiSelect = true
  488.     dlg.title = caption
  489.  
  490.     local p = getFilenamePath filename
  491.     if doesFileExist p then
  492.     dlg.initialDirectory = p
  493.  
  494.     -- MAXScript getOpenFilename uses trailing |;
  495.     -- OpenFileDialog filter does not.
  496.     if types == "|" then
  497.         dlg.filter = (substring types 1 (types.count - 1))
  498.     else
  499.         dlg.filter = types
  500.  
  501.     dlg.filterIndex = default
  502.  
  503.     local result = dlg.ShowDialog()
  504.     if (result.Equals result.OK) then
  505.         dlg.filenames
  506.     else
  507.         undefined
  508. )
  509.  
  510. -------------------------------------------------------------------------------
  511. --    ActorX data structures
  512. -------------------------------------------------------------------------------
  513.  
  514. struct VChunkHeader
  515. (
  516.     ChunkID,
  517.     TypeFlag,
  518.     DataSize,
  519.     DataCount
  520. )
  521.  
  522. fn ReadChunkHeader bstream =
  523. (
  524.     local hdr = VChunkHeader ()
  525.     hdr.ChunkID   = ReadFixedString bstream 20
  526.     hdr.TypeFlag  = ReadLong bstream #unsigned
  527.     hdr.DataSize  = ReadLong bstream #unsigned
  528.     hdr.DataCount = ReadLong bstream #unsigned
  529. --    format "Read chunk header: %\n" hdr
  530.     hdr
  531. )
  532.  
  533. struct VVertex
  534. (
  535.     PointIndex,
  536.     U, V,
  537.     MatIndex,
  538.     Reserved,
  539.     Pad
  540. )
  541.  
  542. fn ReadVVertex bstream =
  543. (
  544.     local v = VVertex ()
  545.     local pad
  546.     v.PointIndex = ReadShort bstream #unsigned
  547.     pad          = ReadShort bstream
  548.     v.U          = ReadFloat bstream
  549.     v.V          = ReadFloat bstream
  550.     v.MatIndex   = ReadByte  bstream #unsigned
  551.     v.Reserved   = ReadByte  bstream #unsigned
  552.     v.Pad        = ReadShort bstream #unsigned
  553.     v
  554. )
  555.  
  556. fn ReadVVertex32 bstream =
  557. (
  558.     local v = VVertex ()
  559.     v.PointIndex = ReadLong  bstream #unsigned            -- short -> long, no "pad"
  560.     v.U          = ReadFloat bstream
  561.     v.V          = ReadFloat bstream
  562.     v.MatIndex   = ReadByte  bstream #unsigned
  563.     v.Reserved   = ReadByte  bstream #unsigned
  564.     v.Pad        = ReadShort bstream #unsigned
  565.     v
  566. )
  567.  
  568. struct VTriangle
  569. (
  570.     Wedge0, Wedge1, Wedge2,
  571.     MatIndex,
  572.     AuxMatIndex,
  573.     SmoothingGroups
  574. )
  575.  
  576. fn ReadVTriangle bstream =
  577. (
  578.     local v = VTriangle ()
  579.     v.Wedge0          = ReadShort bstream #unsigned
  580.     v.Wedge1          = ReadShort bstream #unsigned
  581.     v.Wedge2          = ReadShort bstream #unsigned
  582.     v.MatIndex        = ReadByte  bstream #unsigned
  583.     v.AuxMatIndex     = ReadByte  bstream #unsigned
  584.     v.SmoothingGroups = ReadLong  bstream #unsigned
  585.     v
  586. )
  587.  
  588. fn ReadVTriangle32 bstream =
  589. (
  590.     local v = VTriangle ()
  591.     v.Wedge0          = ReadLong  bstream #unsigned        -- short -> long
  592.     v.Wedge1          = ReadLong  bstream #unsigned        -- ...
  593.     v.Wedge2          = ReadLong  bstream #unsigned        -- ...
  594.     v.MatIndex        = ReadByte  bstream #unsigned
  595.     v.AuxMatIndex     = ReadByte  bstream #unsigned
  596.     v.SmoothingGroups = ReadLong  bstream #unsigned
  597.     v
  598. )
  599.  
  600. struct VMaterial
  601. (
  602.     MaterialName,
  603.     TextureIndex,
  604.     PolyFlags,
  605.     AuxMaterial,
  606.     AuxFlags,
  607.     LodBias,
  608.     LodStyle
  609. )
  610.  
  611. fn ReadVMaterial bstream =
  612. (
  613.     local m = VMaterial ()
  614.     m.MaterialName = ReadFixedString bstream 64
  615.     m.TextureIndex = ReadLong bstream #unsigned
  616.     m.PolyFlags    = ReadLong bstream #unsigned
  617.     m.AuxMaterial  = ReadLong bstream #unsigned
  618.     m.AuxFlags     = ReadLong bstream #unsigned
  619.     m.LodBias      = ReadLong bstream
  620.     m.LodStyle     = ReadLong bstream
  621.     m
  622. )
  623.  
  624.  
  625. struct VBone
  626. (
  627.     Name,
  628.     Flags,
  629.     NumChildren,
  630.     ParentIndex,
  631.     -- VJointPos
  632.     Orientation,
  633.     Position,
  634.     Length,
  635.     Size,
  636.     -- Computed data
  637.     Matrix
  638. )
  639.  
  640. fn ReadVBone bstream =
  641. (
  642.     local b = VBone ()
  643.     b.Name        = ReadFixedString bstream 64
  644.     b.Flags       = ReadLong    bstream #unsigned
  645.     b.NumChildren = ReadLong    bstream
  646.     b.ParentIndex = ReadLong    bstream
  647.     b.Orientation = ReadFQuat   bstream
  648.     b.Position    = ReadFVector bstream
  649.     b.Length      = ReadFloat   bstream
  650.     b.Size        = ReadFVector bstream
  651.     b
  652. )
  653.  
  654.  
  655. struct VRawBoneInfluence
  656. (
  657.     Weight,
  658.     PointIndex,
  659.     BoneIndex
  660. )
  661.  
  662. fn ReadVRawBoneInfluence bstream =
  663. (
  664.     local v = VRawBoneInfluence ()
  665.     v.Weight     = ReadFloat bstream
  666.     v.PointIndex = ReadLong bstream #unsigned
  667.     v.BoneIndex  = ReadLong bstream #unsigned
  668.     v
  669. )
  670.  
  671. fn InfluenceSort v1 v2 =
  672. (
  673.     local cmp = v1.PointIndex - v2.PointIndex
  674.     if (cmp == 0) then cmp = v1.BoneIndex - v2.BoneIndex
  675.     cmp
  676. )
  677.  
  678.  
  679. struct AnimInfoBinary
  680. (
  681.     Name,
  682.     Group,
  683.     TotalBones,
  684.     RootInclude,
  685.     KeyCompressionStyle,
  686.     KeyQuotum,
  687.     KeyReduction,
  688.     TrackTime,
  689.     AnimRate,
  690.     StartBone,
  691.     FirstRawFrame,
  692.     NumRawFrames
  693. )
  694.  
  695. fn ReadAnimInfoBinary bstream =
  696. (
  697.     v = AnimInfoBinary ()
  698.     v.Name                = ReadFixedString bstream 64
  699.     v.Group               = ReadFixedString bstream 64
  700.     v.TotalBones          = ReadLong  bstream
  701.     v.RootInclude         = ReadLong  bstream
  702.     v.KeyCompressionStyle = ReadLong  bstream
  703.     v.KeyQuotum           = ReadLong  bstream
  704.     v.KeyReduction        = ReadFloat bstream
  705.     v.TrackTime           = ReadFloat bstream
  706.     v.AnimRate            = ReadFloat bstream
  707.     v.StartBone           = ReadLong  bstream
  708.     v.FirstRawFrame       = ReadLong  bstream
  709.     v.NumRawFrames        = ReadLong  bstream
  710.     v
  711. )
  712.  
  713.  
  714. struct VQuatAnimKey
  715. (
  716.     Position,
  717.     Orientation,
  718.     Time
  719. )
  720.  
  721. fn ReadVQuatAnimKey bstream =
  722. (
  723.     local k = VQuatAnimKey ()
  724.     k.Position    = ReadFVector bstream
  725.     k.Orientation = ReadFQuat   bstream
  726.     k.Time        = ReadFloat   bstream
  727.     k
  728. )
  729.  
  730.  
  731. -------------------------------------------------------------------------------
  732. --    Bone attributes
  733. -------------------------------------------------------------------------------
  734.  
  735. AXBoneCustomDataDef = attributes AXBoneCustomData
  736. attribID:#(0xF3DD7FCD, 0x4DB58449)
  737. (
  738.     parameters BindPose
  739.     (
  740.         AX_RelMatrix    type: #matrix3        -- matrix relative to parent bone
  741.         AX_WorldMatrix    type: #matrix3        -- world matrix
  742.     )
  743. )
  744.  
  745. -------------------------------------------------------------------------------
  746. --    Loading materials
  747. -------------------------------------------------------------------------------
  748.  
  749. fn axFindTexture texDir baseName =
  750. (
  751.     -- DDS
  752.     foundTex = axFindFile texDir (baseName + ".dds") recurse:g_texRecurse
  753.     if foundTex == undefined then
  754.     (
  755.         -- TGA
  756.         foundTex = axFindFile texDir (baseName + ".tga") recurse:g_texRecurse
  757. /*        if foundTex == undefined then
  758.         (
  759.             -- other formats?
  760.         ) */
  761.     )
  762.     foundTex
  763. )
  764.  
  765. fn axImportMaterial matName texDir =
  766. (
  767.     local subMat = standardMaterial name:matName
  768.  
  769.     local texFilename
  770.     local foundTex
  771.  
  772.     -- try to file material file
  773.     texFilename = matName + ".mat"
  774.     foundTex = axFindFile texDir texFilename recurse:g_texRecurse
  775.     if foundTex != undefined then
  776.     (
  777.         texFilename = foundTex
  778.         format "Loading material %\n" texFilename
  779.         local matFile = openFile texFilename
  780.         while eof matFile == false do
  781.         (
  782.             local line = readline matFile
  783.             local tok = filterString line " ="
  784. --            format "[%] = [%]\n" tok[1] tok[2]
  785.             local parm = tok[1]
  786.             local file = tok[2]
  787.             foundTex = axFindTexture texDir file
  788.             if foundTex == undefined then continue
  789.             local bitmap = bitmapTexture name:foundTex fileName:foundTex
  790.             if parm == "Normal" then
  791.             (
  792.                 local normalMap = normal_bump name:foundTex normal_map:bitmap
  793.                 subMat.bumpMap = normalMap
  794.                 subMat.bumpMapAmount = 100        -- amount is set to 30 by default
  795.             )
  796.             else
  797.             (
  798.                 if parm == "Diffuse"   then subMat.diffuseMap = bitmap
  799.                 if parm == "Specular"  then subMat.specularMap = bitmap
  800.                 if parm == "SpecPower" then subMat.specularLevelMap = bitmap
  801.                 if parm == "Opacity"   then subMat.opacityMap = bitmap
  802.                 if parm == "Emissive"  then subMat.selfIllumMap = bitmap
  803.             )
  804.         )
  805.         close matFile
  806.         return subMat
  807.     )
  808.     -- no material file found, try simple texture
  809.     -- get texture filename
  810.     texFilename = matName
  811.     foundTex = axFindTexture texDir matName
  812.     if foundTex != undefined then
  813.     (
  814.         texFilename = foundTex
  815.     )
  816.     else
  817.     (
  818.         if g_texMissAction == 2 then            -- ask
  819.         (
  820.             local check = getOpenFileName caption:("Get texture for material " + matName) \
  821.                 types:"Texture files (*.tga,*.dds)|*.tga;*.dds|All (*.*)|*.*|" filename:texFilename
  822.             if check != undefined then texFilename = check
  823.         )
  824.     )
  825.     if not doesFileExist texFilename then format "Unable to find texture %\n" texFilename
  826.     -- continue setup (even in a case of error)
  827.     local bitmap = bitmapTexture name:texFilename fileName:texFilename
  828.     subMat.diffuseMap = bitmap
  829.     -- return
  830.     subMat
  831. )
  832.  
  833. -------------------------------------------------------------------------------
  834. --    MAX helpers
  835. -------------------------------------------------------------------------------
  836.  
  837. fn FindAllBones_Recurse bones parent =
  838. (
  839.     for i = 1 to parent.children.count do
  840.     (
  841.         node = parent.children[i]
  842.         if isKindOf node BoneObj then
  843.         (
  844.             append bones node
  845.         )
  846.         FindAllBones_Recurse bones node
  847.     )
  848. )
  849.  
  850. fn FindAllBones =
  851. (
  852.     local bones = #()
  853.  
  854.     FindAllBones_Recurse bones rootNode
  855.  
  856.     bones
  857. )
  858.  
  859. fn RemoveAnimation =
  860. (
  861.     stopAnimation()
  862.     bones = FindAllBones()
  863.     for i = 1 to bones.count do
  864.     (
  865.         b = bones[i]
  866.         deleteKeys b #allKeys
  867.     )
  868.     animationRange = interval 0 1
  869. )
  870.  
  871.  
  872. fn RestoreBindpose =
  873. (
  874.     RemoveAnimation()
  875.  
  876.     try
  877.     (
  878.         local rotMatrix = axGetRootMatrix()
  879.         -- note: should rotate every bone because we are not applying parent's rotation here
  880.         -- find bones
  881.         bones = FindAllBones()
  882.         for i = 1 to bones.count do
  883.         (
  884.             b = bones[i]
  885.             data = custAttributes.get b AXBoneCustomDataDef
  886.             if data != undefined then
  887.             (
  888.                 b.transform = data.AX_WorldMatrix * rotMatrix
  889.             )
  890. --            else
  891. --            (
  892. --                format "no info for %\n" b.name
  893. --            )
  894.         )
  895.  
  896.         set coordsys world
  897.     )
  898.     catch
  899.     (
  900.         format "ERROR!\n"
  901.     )
  902. )
  903.  
  904.  
  905. fn ClearMaxScene =
  906. (
  907.     max select all
  908.     if $ != undefined then delete $
  909. )
  910.  
  911.  
  912. -------------------------------------------------------------------------------
  913. --    Loading PSK file
  914. -------------------------------------------------------------------------------
  915.  
  916. fn ImportPskFile filename skelOnly:false =
  917. (
  918.     set coordsys world
  919.  
  920.     local Verts     = #()
  921.     local Wedges    = #()
  922.     local Tris      = #()
  923.     local Materials = #()
  924.     local MeshBones = #()
  925.     local Infs      = #()
  926.  
  927.     --------- Read the file ---------
  928.  
  929.     local numVerts      = 0
  930.     local numWedges     = 0
  931.     local numTris       = 0
  932.     local numMaterials  = 0
  933.     local numBones      = 0
  934.     local numInfluences = 0
  935.     local numTexCoords  = 1
  936.  
  937.     local extraUV = #()
  938.  
  939.     local profileStartTime = timeStamp()
  940.  
  941.     try
  942.     (
  943.         file = fopen filename "rb"
  944.         if file == undefined then return undefined
  945.  
  946.         -- First header --
  947.         hdr = ReadChunkHeader file
  948.         if (hdr.ChunkID != "ACTRHEAD") then
  949.         (
  950.             ErrorMessage("Bad chunk header: \"" + hdr.ChunkID + "\"")
  951.         )
  952.  
  953.         while not IsEndOfFile(file) do
  954.         (
  955.             hdr = ReadChunkHeader file
  956.             local chunkID = hdr.ChunkID
  957.             -- check for extra UV set from latest ActorX exporter
  958.             -- note: data has the same format as pskx extension, so the same loading code is used
  959.             if (chunkID == "EXTRAUVS0") or (chunkID == "EXTRAUVS1") or (chunkID == "EXTRAUVS2") then
  960.                 chunkID = "EXTRAUV0";
  961. --            format "Chunk: % (% items, % bytes/item, pos %)\n" hdr.ChunkID hdr.DataCount hdr.DataSize (ftell file)
  962.             case chunkID of
  963.             (
  964.             -- Points --
  965.             "PNTS0000":
  966.                 (
  967.                     numVerts = hdr.DataCount
  968.                     Verts[numVerts] = [ 0, 0, 0 ]        -- preallocate
  969.                     for i = 1 to numVerts do Verts[i] = ReadFVector file
  970.                 )
  971.  
  972.             -- Wedges --
  973.             "VTXW0000":
  974.                 (
  975.                     numWedges = hdr.DataCount
  976.                     Wedges[numWedges] = VVertex ()        -- preallocate
  977.                     if numWedges <= 65536 then
  978.                     (
  979.                         for i = 1 to numWedges do Wedges[i] = ReadVVertex file
  980.                     )
  981.                     else
  982.                     (
  983.                         for i = 1 to numWedges do Wedges[i] = ReadVVertex32 file
  984.                     )
  985.                 )
  986.  
  987.             -- Faces --
  988.             "FACE0000":
  989.                 (
  990.                     numTris = hdr.DataCount
  991.                     Tris[numTris] = VTriangle ()        -- preallocate
  992.                     for i = 1 to numTris do Tris[i] = ReadVTriangle file
  993.                 )
  994.  
  995.             -- Faces32 --
  996.             "FACE3200":
  997.                 (
  998.                     numTris = hdr.DataCount
  999.                     Tris[numTris] = VTriangle ()        -- preallocate
  1000.                     for i = 1 to numTris do Tris[i] = ReadVTriangle32 file
  1001.                 )
  1002.  
  1003.             -- Materials --
  1004.             "MATT0000":
  1005.                 (
  1006.                     numMaterials = hdr.DataCount
  1007.                     Materials[numMaterials] = VMaterial ()    -- preallocate
  1008.                     for i = 1 to numMaterials do Materials[i] = ReadVMaterial file
  1009.                 )
  1010.  
  1011.             -- Bones --
  1012.             "REFSKELT":
  1013.                 (
  1014.                     numBones = hdr.DataCount
  1015.                     if numBones > 0 then MeshBones[numBones] = VBone () -- preallocate
  1016.                     for i = 1 to numBones do
  1017.                     (
  1018.                         MeshBones[i] = ReadVBone file
  1019. --                            format "Bone[%] = %\n" (i-1) MeshBones[i].Name
  1020.                     )
  1021.                     axFixBoneNames MeshBones
  1022.                 )
  1023.  
  1024.             -- Weights --
  1025.             "RAWWEIGHTS":
  1026.                 (
  1027.                     numInfluences = hdr.DataCount
  1028.                     if numInfluences > 0 then Infs[numInfluences] = VRawBoneInfluence () -- preallocate
  1029.                     for i = 1 to numInfluences do Infs[i] = ReadVRawBoneInfluence file
  1030.                 )
  1031.  
  1032.             -- additional UV set
  1033.             "EXTRAUV0":
  1034.                 (
  1035.                     numUVVerts = hdr.DataCount
  1036.                     if (numUVVerts != numWedges) then ErrorMessage("Bad vertex count for extra UV set")
  1037.                     local UV = #()
  1038.                     UV[numUVVerts] = [ 0, 0 ]
  1039.                     for i = 1 to numUVVerts do UV[i] = ReadVector2 file
  1040.                     extraUV[numTexCoords] = UV
  1041.                     numTexCoords = numTexCoords + 1
  1042.                 )
  1043.  
  1044.             default:
  1045.                 (
  1046.                     -- skip unknown chunk
  1047.                     format "Unknown chunk header: \"%\" at %\n" hdr.ChunkID (ftell file)
  1048.                     fseek file (hdr.DataSize * hdr.DataCount) #seek_cur
  1049.                 )
  1050.             )
  1051.         )
  1052.     )
  1053.     catch
  1054.     (
  1055.         fclose file
  1056.         messageBox("Error loading file " + filename)
  1057.         format "FATAL ERROR: %\n" (getCurrentException())
  1058.         return undefined
  1059.     )
  1060.  
  1061.     format "Read mesh: % verts, % wedges, % tris, % materials, % bones, % influences\n" \
  1062.         numVerts numWedges numTris numMaterials numBones numInfluences
  1063.     fclose file
  1064.  
  1065.     --------- File is completely read now ---------
  1066.  
  1067.     -- generate skeleton
  1068.     MaxBones = #()
  1069.     local rotMatrix = matrix3 1
  1070.     for i = 1 to numBones do
  1071.     (
  1072.         bn = MeshBones[i]
  1073.         -- build bone matrix
  1074.         q = bn.Orientation
  1075.         if ((i == 1) and not g_dontConjugateRoot) then q = conjugate q
  1076.         mat = (normalize q) as matrix3
  1077.         mat.row4 = bn.Position * g_meshScale
  1078.         -- transform from parent bone coordinate space to world space
  1079.         if (i > 1) then
  1080.         (
  1081.             bn.Matrix = mat * MeshBones[bn.ParentIndex + 1].Matrix
  1082.         )
  1083.         else
  1084.         (
  1085.             bn.Matrix = mat
  1086.         )
  1087.  
  1088.         -- get bone length (just for visual appearance)
  1089.         childBone = axFindFirstChild MeshBones i
  1090.         if (childBone != undefined) then
  1091.         (
  1092.             len = (length childBone.Position) * g_meshScale
  1093.         )
  1094.         else
  1095.         (
  1096.             len = 4        -- no children, default length; note: when len = 1 has bugs with these bones!
  1097.         )
  1098.         if len < 4 then len = 4
  1099.         -- create Max bone
  1100.         newBone = getNodeByName bn.Name exact:true ignoreCase:true
  1101.         if (newBone == undefined) then
  1102.         (
  1103.             if (g_reorientBones == false or childBone == undefined) then
  1104.             (
  1105.                 -- create new bone
  1106.                 newBone = bonesys.createbone    \
  1107.                       bn.Matrix.row4            \
  1108.                       (bn.Matrix.row4 + len * (normalize bn.Matrix.row1)) \
  1109.                       (normalize bn.Matrix.row3)
  1110.             )
  1111.             else
  1112.             (
  1113.                 -- reorient bone matrix to point directly to a child
  1114.                 -- get world position of the child bone
  1115.                 local childPos = childBone.Position * bn.Matrix * g_meshScale
  1116.                 newBone = bonesys.createbone    \
  1117.                       bn.Matrix.row4            \
  1118.                       childPos                    \
  1119.                       bn.Matrix.row3
  1120.             )
  1121.             newBone.name   = bn.Name
  1122.             newBone.width  = g_boneSize
  1123.             newBone.height = g_boneSize
  1124.             newBone.setBoneEnable false 0                    -- this is a required thing, otherwise a lot of problems would appear
  1125.             newBone.pos.controller      = TCB_position ()
  1126.             newBone.rotation.controller = TCB_rotation ()    -- required for correct animation
  1127.             -- setup parent
  1128.             if (i > 1) then
  1129.             (
  1130.                 if (bn.ParentIndex >= i) then
  1131.                 (
  1132.                     format "Invalid parent % for bone % (%)" bn.ParentIndex (i-1) bn.Name
  1133.                     return undefined
  1134.                 )
  1135.                 newBone.parent = MaxBones[bn.ParentIndex + 1]
  1136.             )
  1137.             -- store bind pose in custom data block
  1138.             custAttributes.add newBone AXBoneCustomDataDef
  1139.             mat = (normalize q) as matrix3        -- rebuild 'mat', but without scale
  1140.             mat.row4 = bn.Position
  1141.             newBone.AX_RelMatrix   = mat
  1142.             newBone.AX_WorldMatrix = bn.Matrix
  1143.         )
  1144.         else
  1145.         (
  1146.             -- bone already exists
  1147.             if g_reposBones then newBone.transform = bn.Matrix
  1148.         )
  1149.         MaxBones[i] = newBone
  1150.     )
  1151.  
  1152.     -- generate mesh
  1153.     MaxFaces = #()
  1154.     MaxVerts = #()
  1155.     MaxFaces[numTris]   = [ 0, 0, 0 ]            -- preallocate
  1156.     MaxVerts[numWedges] = [ 0, 0, 0 ]            -- ...
  1157.     VertList = #();                                -- list of wedges linked for each vertex
  1158.     VertList.count = numVerts                    -- preallocate
  1159.     for i = 1 to numVerts do VertList[i] = #()    -- initialize with empty array
  1160.     for i = 1 to numWedges do
  1161.     (
  1162.         local vertId = Wedges[i].PointIndex + 1
  1163.         MaxVerts[i] = Verts[vertId] * g_meshScale
  1164.         append VertList[vertId] i
  1165.     )
  1166.     for i = 1 to numTris do
  1167.     (
  1168.         tri = Tris[i]
  1169.         w0 = tri.Wedge0
  1170.         w1 = tri.Wedge1
  1171.         w2 = tri.Wedge2
  1172.         MaxFaces[i] = [ w1+1, w0+1, w2+1 ]        -- note: reversing vertex order
  1173.     )
  1174.     newMesh = mesh vertices:MaxVerts faces:MaxFaces name:(getFilenameFile filename)
  1175.     -- texturing
  1176.     newMesh.xray = g_seeThru
  1177.     meshop.setNumMaps newMesh (numTexCoords+1)    -- 0 is vertex color, 1+ are textures
  1178.     meshop.setMapSupport newMesh 1 true            -- enable texturemap channel
  1179.     meshop.setNumMapVerts newMesh 1 numWedges    -- set number of texture vertices
  1180.     for i = 1 to numWedges do
  1181.     (
  1182.         -- set texture coordinates
  1183.         w = Wedges[i]
  1184.         meshop.setMapVert newMesh 1 i [ w.U, 1-w.V, 1-w.V ]    -- V coordinate is flipped
  1185.     )
  1186.     for i = 1 to numTris do
  1187.     (
  1188.         -- setup face vertices and material
  1189.         tri = Tris[i]
  1190.         meshop.setMapFace newMesh 1 i [ tri.Wedge1+1, tri.Wedge0+1, tri.Wedge2+1 ]
  1191.         setFaceMatId newMesh i (tri.MatIndex+1)
  1192.         setFaceSmoothGroup newMesh i tri.SmoothingGroups
  1193.     )
  1194.     -- extra UV sets (code is similar to above!)
  1195.     for j = 2 to numTexCoords do
  1196.     (
  1197.         format "Loading UV set #% ...\n" j
  1198.         uvSet = extraUV[j-1]                        -- extraUV does not holds 1st UV set
  1199.         meshop.setMapSupport newMesh j true            -- enable texturemap channel
  1200.         meshop.setNumMapVerts newMesh j numWedges    -- set number of texture vertices
  1201.         for i = 1 to numWedges do
  1202.         (
  1203.             -- set texture coordinates
  1204.             uv = uvSet[i]
  1205.             meshop.setMapVert newMesh j i [ uv.x, 1-uv.y, 1-uv.y ]    -- V coordinate is flipped
  1206.         )
  1207.     )
  1208.  
  1209.     newMat = multiMaterial numsubs:numMaterials
  1210.     if g_skelOnly then numMaterials = 0        -- do not load materials for this option
  1211.     for i = 1 to numMaterials do
  1212.     (
  1213.         local texDir
  1214.         if g_texDir != "" then
  1215.         (
  1216.             texDir = g_texDir
  1217.         )
  1218.         else
  1219.         (
  1220.             texDir = getFilenamePath filename
  1221.         )
  1222.         local subMat = axImportMaterial Materials[i].MaterialName texDir
  1223.         newMat.materialList[i] = subMat
  1224.         showTextureMap subMat true
  1225. --        format "Material[%] = %\n" i Materials[i].MaterialName
  1226.     )
  1227.     newMesh.material = newMat
  1228.  
  1229.     update newMesh
  1230.  
  1231.     -- smooth vertex normals accross UV seams
  1232.     max modify mode
  1233.     select newMesh
  1234.  
  1235.     normalMod = editNormals ()
  1236.     addModifier newMesh normalMod
  1237.     normalMod.selectBy = 1
  1238.  
  1239.     for i = 1 to VertList.count do
  1240.     (
  1241.         if VertList[i].count > 1 then
  1242.         (
  1243.             local seamWedges = VertList[i] as bitArray
  1244.             local n = #{}
  1245.             normalMod.ConvertVertexSelection &seamWedges &n
  1246.             normalMod.Average selection:n
  1247.         )
  1248.     )
  1249.     VertList.count = 0
  1250.     collapsestack newMesh
  1251.  
  1252.     -- generate skin modifier
  1253.     skinMod = skin ()
  1254.     boneIDMap = #()
  1255.     if numBones > 0 then
  1256.     (
  1257.         addModifier newMesh skinMod
  1258.         for i = 1 to numBones do
  1259.         (
  1260.             if i != numBones then
  1261.                 skinOps.addBone skinMod MaxBones[i] 0
  1262.             else
  1263.                 skinOps.addBone skinMod MaxBones[i] 1
  1264.         )
  1265.         -- In Max 2013 the bone IDs are scrambled, so we look them up
  1266.         -- by bone's name and stores them in a table.
  1267.         local numSkinBones = skinOps.GetNumberBones skinMod
  1268.         -- iterate all bones in the Max (could be more than in a mesh)
  1269.         for i = 1 to numSkinBones do
  1270.         (
  1271.             local boneName = skinOps.GetBoneName skinMod i 0
  1272.             -- compare with mesh bones by name
  1273.             for j = 1 to numBones do
  1274.             (
  1275.                 if boneName == MeshBones[j].Name then
  1276.                 (
  1277.                     boneIDMap[j] = i
  1278. --                    format "MaxID[%]: %, OriginalID: %\n" i boneName j
  1279.                     j = numBones + 1 -- break the loop (faster than 'exit')
  1280.                 )
  1281.             )
  1282.         )
  1283.     )
  1284.  
  1285.     if skelOnly then
  1286.     (
  1287.         delete newMesh        -- non-optimal way, may skip mesh creation
  1288.         return undefined
  1289.     )
  1290.     if numBones <= 0 then
  1291.     (
  1292.         return undefined
  1293.     )
  1294.  
  1295. --    redrawViews()
  1296.  
  1297.     modPanel.setCurrentObject skinMod
  1298.  
  1299.     -- setup vertex influences (weights)
  1300.     qsort Infs InfluenceSort
  1301.  
  1302.     -- build vertex to influence map
  1303.     vertInfStart = #()
  1304.     vertInfNum   = #()
  1305.     vertInfStart[numVerts] = 0        -- preallocate
  1306.     vertInfNum[numVerts]   = 0        -- ...
  1307.     count = 0
  1308.     for i = 1 to numInfluences do
  1309.     (
  1310.         v     = Infs[i]
  1311.         vert  = v.PointIndex+1
  1312.         count += 1
  1313.         if (i == numInfluences) or (Infs[i+1].PointIndex+1 != vert) then
  1314.         (
  1315.             -- flush
  1316.             vertInfStart[vert] = i - count + 1
  1317.             vertInfNum[vert]   = count
  1318.             count = 0
  1319.         )
  1320.     )
  1321.  
  1322. --    progressStart "Setting weights ..." -- shouldn't call progress functions, causes crash in script
  1323.     disableSceneRedraw()
  1324.     numRepairedVerts = 0
  1325.     numBadVerts = 0
  1326.     try
  1327.     (
  1328.  
  1329.         for wedge = 1 to numWedges do
  1330.         (
  1331.             vert    = Wedges[wedge].PointIndex+1
  1332.             start   = vertInfStart[vert]
  1333.             numInfs = vertInfNum[vert]
  1334.             if numInfs == undefined then
  1335.             (
  1336.                 numInfs = 0
  1337.                 format "Vertex % (wedge %) has no weights\n" (vert-1) (wedge-1)
  1338.             )
  1339.  
  1340. /*
  1341.             -- This code uses SetVertexWeights
  1342.             oldBone = skinOps.GetVertexWeightBoneID skinMod wedge 1
  1343.             numWeights = skinOps.GetVertexWeightCount skinMod wedge
  1344.             if numWeights > 1 then
  1345.             (
  1346.                 skinOps.ReplaceVertexWeights skinMod wedge oldBone 1
  1347.             )
  1348.             for i = 1 to numInfs do
  1349.             (
  1350.                 v = Infs[start + i - 1]
  1351.                 b = boneIDMap[v.BoneIndex+1]
  1352. --                format "Inf %(%) % : %\n" wedge vert MeshBones[b].Name v.Weight
  1353.                 skinOps.SetVertexWeights skinMod wedge b v.Weight
  1354.                 if b == oldBone then
  1355.                 (
  1356.                     oldBone = -1
  1357.                 )
  1358.             )
  1359.             if oldBone > 0 then
  1360.             (
  1361.                 skinOps.SetVertexWeights skinMod wedge oldBone 0
  1362.             )
  1363. */
  1364.             -- This code uses ReplaceVertexWeights with arrays, a few times slower;
  1365.             -- it is still here in a case of bugs with SetVertexWeights path
  1366.             infBones   = #()
  1367.             infWeights = #()
  1368.             for i = 1 to numInfs do
  1369.             (
  1370.                 v = Infs[start + i - 1]
  1371.                 append infBones   boneIDMap[v.BoneIndex + 1]
  1372.                 append infWeights v.Weight
  1373.             )
  1374.             skinOps.ReplaceVertexWeights skinMod wedge infBones infWeights
  1375.             -- NOTE: older Max versions after ReplaceVertexWeights call performed reset of infBones and
  1376.             -- infWeights arrays, so we wasn't able to reuse them. At least Max 2015 doesn't do that.
  1377.  
  1378.             -- Check is weights were set correctly
  1379.             numWeights = skinOps.GetVertexWeightCount skinMod wedge
  1380.             if numWeights != numInfs then
  1381.             (
  1382.                 -- We've tried to set weights for this vertex, but MaxScript decided to keep
  1383.                 -- other bones as dependency (bug in ReplaceVertexWeights). Try to repair:
  1384.                 -- enumerate all current weights and set unwanted bone weights to 0 explicitly.
  1385.                 -- Note: it looks like this is not an issue for Max 2014, it appears in 2015:
  1386.                 -- https://trello.com/c/76npwkAY/115-possible-bug-with-importer-on-max-2015
  1387. --                format "Bad vertex: % bones(%) but %\n" wedge numInfs numWeights
  1388.                 for w = 1 to numWeights do
  1389.                 (
  1390.                     bone = skinOps.GetVertexWeightBoneID skinMod wedge w
  1391.                     found = findItem infBones bone
  1392.                     if found == 0 then
  1393.                     (
  1394.                         append infBones bone
  1395.                         append infWeights 0
  1396.                     )
  1397.                 )
  1398.                 skinOps.ReplaceVertexWeights skinMod wedge infBones infWeights
  1399.                 numWeights = skinOps.GetVertexWeightCount skinMod wedge
  1400.                 if numWeights != numInfs then
  1401.                 (
  1402. --                    format "Bad vertex: %: bones(%) weights(%)\n" wedge infBones infWeights
  1403.                     numBadVerts += 1
  1404.                 )
  1405.                 else
  1406.                 (
  1407.                     numRepairedVerts += 1
  1408.                 )
  1409.             )
  1410. --            progressUpdate (100.0 * wedge / numWedges)
  1411.         )
  1412.     )
  1413.     catch
  1414.     (
  1415.         enableSceneRedraw()
  1416. --        progressEnd()
  1417.         throw()
  1418.     )
  1419.     enableSceneRedraw()
  1420. --    progressEnd()
  1421.     if (numRepairedVerts > 0) or (numBadVerts > 0) then
  1422.     (
  1423.         format "Problems during skinning: % bad vertices, % repaired vertices\n" numBadVerts numRepairedVerts
  1424.     )
  1425.  
  1426.     -- apply mesh rotation
  1427.     if numBones >= 1 then
  1428.     (
  1429.         MaxBones[1].transform = MaxBones[1].transform * axGetRootMatrix()
  1430.     )
  1431.  
  1432.     local profileEndTime = timeStamp()
  1433.     format "Loaded in % sec\n" ((profileEndTime - profileStartTime) / 1000.0)
  1434.  
  1435.     gc()
  1436. )
  1437.  
  1438.  
  1439. -------------------------------------------------------------------------------
  1440. --    Loading PSA file
  1441. -------------------------------------------------------------------------------
  1442.  
  1443. fn FindPsaTrackIndex Anims Name =
  1444. (
  1445.     local notfound = true, res = -1
  1446.     for i = 1 to Anims.count while notfound do
  1447.     (
  1448.         if Anims[i].Name == Name then
  1449.         (
  1450.             res = i
  1451.             notfound = false
  1452.         )
  1453.     )
  1454.     res
  1455. )
  1456.  
  1457. fn FindPsaBoneIndex Bones Name =
  1458. (
  1459.     local notfound = true, res = -1
  1460.     for i = 1 to Bones.count while notfound do
  1461.     (
  1462.         if Bones[i].Name == Name then
  1463.         (
  1464.             res = i
  1465.             notfound = false
  1466.         )
  1467.     )
  1468.     res
  1469. )
  1470.  
  1471. -- UseAnimTranslation[] is array of flags signalling that particular bone should use translation
  1472. -- from the animation; when value is set to false, mesh translation will be used
  1473. fn LoadPsaConfig filename Anims Bones UseAnimTranslation AnimFlags =
  1474. (
  1475.     -- allocate and initialize UseAnimTranslation array
  1476.     UseAnimTranslation[Bones.count] = true            -- preallocate
  1477.     for i = 1 to Bones.count do UseAnimTranslation[i] = true
  1478.     -- root bone is always translated, start with index 2 below
  1479.  
  1480.     case g_animTransMode of
  1481.     (
  1482. --    1: - use from AnimSet, do nothing here
  1483.     2:    (
  1484.             for i = 2 to Bones.count do UseAnimTranslation[i] = false
  1485.             return undefined
  1486.         )
  1487.     3:    (
  1488.             for i = 2 to Bones.count do UseAnimTranslation[i] = true    -- old behaviour - everything will be taken from the animation
  1489.             return undefined
  1490.         )
  1491.     )
  1492.  
  1493.     -- read configuration file
  1494.     local cfgFile = openFile filename
  1495.     if cfgFile == undefined then return undefined
  1496.  
  1497.     local mode = 0
  1498.  
  1499.     while eof cfgFile == false do
  1500.     (
  1501.         local line = readline cfgFile
  1502.  
  1503.         -- process directove
  1504.         case line of
  1505.         (
  1506.         "": continue        -- empty line
  1507.         "[AnimSet]": ( mode = 1; continue )
  1508.         "[UseTranslationBoneNames]": ( mode = 2; continue )
  1509.         "[ForceMeshTranslationBoneNames]": ( mode = 3; continue )
  1510.         "[RemoveTracks]":
  1511.             (
  1512.                 mode = 4
  1513.                 -- allocate AnimFlags array, usually not required (currently used for UC2 animations only)
  1514.                 local numKeys = Anims.count * Bones.count
  1515.                 AnimFlags[numKeys] = 0        -- preallocate
  1516.                 for i = 1 to numKeys do AnimFlags[i] = 0
  1517.                 continue
  1518.             )
  1519.         )
  1520.  
  1521.         -- process ordinary line
  1522.         case mode of
  1523.         (
  1524.         0:    ErrorMessage("unexpected \"" + line + "\"")
  1525.  
  1526.         -- AnimSet
  1527.         1:    (
  1528.                 --!! ugly parsing ... but no other params yet
  1529.                 if line == "bAnimRotationOnly=1" then
  1530.                 (
  1531.                     for i = 2 to Bones.count do UseAnimTranslation[i] = false
  1532.                 )
  1533.                 else if line == "bAnimRotationOnly=0" then
  1534.                 (
  1535.                     -- already set to true
  1536.                 )
  1537.                 else
  1538.                 (
  1539.                     ErrorMessage("unexpected AnimSet instruction \"" + line + "\"")
  1540.                 )
  1541.             )
  1542.  
  1543.         -- UseTranslationBoneNames - use translation from animation, useful with bAnimRotationOnly=true only
  1544.         2:    (
  1545.                 local BoneIndex = FindPsaBoneIndex Bones line
  1546.                 if BoneIndex > 0 then
  1547.                 (
  1548.                     UseAnimTranslation[BoneIndex] = true
  1549.                 )
  1550.                 else
  1551.                 (
  1552.                     format "WARNING: UseTranslationBoneNames has specified unknown bone \"%\"\n" line
  1553.                 )
  1554.             )
  1555.  
  1556.         -- ForceMeshTranslationBoneNames - use translation from mesh
  1557.         3:    (
  1558.                 local BoneIndex = FindPsaBoneIndex Bones line
  1559.                 if BoneIndex > 0 then
  1560.                 (
  1561.                     UseAnimTranslation[BoneIndex] = false
  1562.                 )
  1563.                 else
  1564.                 (
  1565.                     format "WARNING: ForceMeshTranslationBoneNames has specified unknown bone \"%\"\n" line
  1566.                 )
  1567.             )
  1568.  
  1569.         -- RemoveTracks
  1570.         4:    (
  1571.                 -- line is in format "SequenceName.BoneIndex=[trans|rot|all]"
  1572.                 local tok1 = filterString line "="            -- [1] = SequenceName.BoneIndex, [2] = Flags
  1573.                 local tok2 = filterString tok1[1] "."        -- [1] = SequenceName, [2] = BoneIndex
  1574.                 local SeqName    = TrimSpaces(tok2[1])
  1575.                 local BoneIdxStr = TrimSpaces(tok2[2])
  1576.                 local Flag       = TrimSpaces(tok1[2])
  1577.                 local SeqIdx = FindPsaTrackIndex Anims SeqName        --?? can cache this value
  1578.                 if SeqIdx <= 0 then ErrorMessage("Animation \"" + SeqName + "\" does not exists" + "\nline:" + line)
  1579.                 FlagIndex = (SeqIdx - 1) * Bones.count + (BoneIdxStr as integer) + 1
  1580.                 if Flag == "trans" then
  1581.                 (
  1582.                     AnimFlags[FlagIndex] = 1    -- NO_TRANSLATION
  1583.                 )
  1584.                 else if Flag == "rot" then
  1585.                 (
  1586.                     AnimFlags[FlagIndex] = 2    -- NO_ROTATION
  1587.                 )
  1588.                 else if Flag == "all" then
  1589.                 (
  1590.                     AnimFlags[FlagIndex] = 3    -- NO_TRANSLATION | NO_ROTATION
  1591.                 )
  1592.                 else
  1593.                 (
  1594.                     ErrorMessage("unknown RemoveTracks flag \"" + Flag + "\"")
  1595.                 )
  1596.             )
  1597.  
  1598.         default:
  1599.             ErrorMessage("unexpected config error")
  1600.         )
  1601.     )
  1602.     close cfgFile
  1603. )
  1604.  
  1605.  
  1606. fn ImportPsaFile filename trackNum all:false =
  1607. (
  1608.     local Bones     = #()
  1609.           Anims     = #()
  1610.  
  1611.     local UseAnimTranslation = #()
  1612.     local AnimFlags          = #()
  1613.  
  1614.     local numBones  = 0
  1615.     local numAnims  = 0
  1616.  
  1617.     local keyPos = 0
  1618.  
  1619.     --------- Read the file ---------
  1620.  
  1621.     try
  1622.     (
  1623.         file = fopen filename "rb"
  1624.         if file == undefined then return undefined
  1625.  
  1626.         -- First header --
  1627.         hdr = ReadChunkHeader file
  1628.         if (hdr.ChunkID != "ANIMHEAD") then
  1629.         (
  1630.             ErrorMessage("Bad chunk header: \"" + hdr.ChunkID + "\"")
  1631.         )
  1632.  
  1633.         while not IsEndOfFile(file) do
  1634.         (
  1635.             hdr = ReadChunkHeader file
  1636. --            format "Chunk: % (% items, % bytes/item, pos %)\n" hdr.ChunkID hdr.DataCount hdr.DataSize (ftell file)
  1637.             case hdr.ChunkID of
  1638.             (
  1639.             -- Bone links --
  1640.             "BONENAMES":
  1641.                 (
  1642.                     numBones = hdr.DataCount
  1643.                     if numBones > 0 then Bones[numBones] = VBone ()        -- preallocate
  1644.                     for i = 1 to numBones do Bones[i] = ReadVBone file
  1645.                 )
  1646.  
  1647.             -- Animation sequence info --
  1648.             "ANIMINFO":
  1649.                 (
  1650.                     numAnims = hdr.DataCount
  1651.                     if numAnims > 0 then Anims[numAnims] = AnimInfoBinary ()    -- preallocate
  1652.                     for i = 1 to numAnims do Anims[i] = ReadAnimInfoBinary file
  1653.  
  1654.                     if trackNum < 0 then
  1655.                     (
  1656.                         -- information only
  1657.                         fclose file
  1658.                         return undefined
  1659.                     )
  1660.                 )
  1661.  
  1662.             -- Key data --
  1663.             "ANIMKEYS":
  1664.                 (
  1665.                     -- determine chunk of the file to load later
  1666.                     if all then trackNum = 1
  1667.                     keyPos = ftell file
  1668.                     for i = 1 to trackNum - 1 do
  1669.                         keyPos += Anims[i].NumRawFrames * numBones * 32
  1670.                     if all then
  1671.                         numFrames = hdr.DataCount / Bones.count
  1672.                     else
  1673.                         numFrames = Anims[trackNum].NumRawFrames
  1674.                     -- skip this chunk
  1675.                     fseek file (hdr.DataSize * hdr.DataCount) #seek_cur
  1676.                 )
  1677.  
  1678.             default:
  1679.                 (
  1680.                     -- skip unknown chunk
  1681.                     format "Unknown chunk header: \"%\" at %\n" hdr.ChunkID (ftell file)
  1682.                     fseek file (hdr.DataSize * hdr.DataCount) #seek_cur
  1683.                 )
  1684.             )
  1685.         )
  1686.     )
  1687.     catch
  1688.     (
  1689.         fclose file
  1690.         messageBox ("Error loading file " + filename)
  1691.         format "FATAL ERROR: %\n" (getCurrentException())
  1692.         throw()
  1693.         return undefined
  1694.     )
  1695.  
  1696.     if numBones < 1 then
  1697.     (
  1698.         format "Animations has no bones\n"
  1699.         return undefined
  1700.     )
  1701.  
  1702.     if keyPos == 0 then
  1703.     (
  1704.         format "No ANIMKEYS chunk was found\n"
  1705.         return undefined
  1706.     )
  1707.  
  1708.     -- find existing scene bones
  1709.     MaxBones     = #()
  1710.     BindPoseInfo = #()
  1711.     SceneBones = FindAllBones()
  1712.     for i = 1 to numBones do
  1713.     (
  1714.         boneName = Bones[i].Name
  1715.         local notfound = true
  1716.         for j = 1 to SceneBones.count while notfound do
  1717.         (
  1718.             b = SceneBones[j]
  1719.             if b.name == boneName then
  1720.             (
  1721.                 MaxBones[i]     = b
  1722.                 BindPoseInfo[i] = custAttributes.get b AXBoneCustomDataDef    -- could be 'undefined'
  1723.                 notfound        = false
  1724.             )
  1725.         )
  1726.  
  1727.         if notfound then
  1728.         (
  1729.             format "WARNING: cannot find bone %\n" boneName
  1730.         )
  1731.         else if BindPoseInfo[i] == undefined then
  1732.         (
  1733.             format "WARNING: cannot get bind pose information for bone %\n" boneName
  1734.         )
  1735.     )
  1736.  
  1737.     -- verify for found root bone
  1738.     if MaxBones[1] == undefined then
  1739.     (
  1740.         messageBox ("WARNING: Unable to find root bone \"" + Bones[1].Name + "\"\nAnimation may appear incorrectly!")
  1741.     )
  1742.  
  1743.     set coordsys world
  1744.     startframe = 0    -- can modify layer ...
  1745.     if g_animAtSlider then
  1746.     (
  1747.         startframe = sliderTime
  1748.     )
  1749.     else
  1750.     (
  1751.         RemoveAnimation()
  1752.     )
  1753.  
  1754.     LoadPsaConfig ( (getFilenamePath filename) + (getFilenameFile filename) + ".config" ) Anims Bones UseAnimTranslation AnimFlags
  1755.  
  1756. /*
  1757.     format "[% trans % flags]\n" UseAnimTranslation.count AnimFlags.count
  1758.     for i = 1 to UseAnimTranslation.count do
  1759.     (
  1760.         if UseAnimTranslation[i] then format "trans: % %\n" i Bones[i].Name
  1761.     )
  1762. */
  1763.  
  1764.     format "Loading track % (%), % keys\n" trackNum Anims[trackNum].Name (numFrames * Bones.count)
  1765.     firstFrame = #()
  1766.     firstFlag  = (trackNum - 1) * numBones + 1
  1767.     flagCount  = AnimFlags.count
  1768.     fseek file keyPos #seek_set                            -- seek to animation keys
  1769.  
  1770.     animate on
  1771.     (
  1772.         progressStart "Loading animation ..."
  1773.         for i = 1 to numFrames do
  1774.         (
  1775.             at time (startframe + i - 1)
  1776.             (
  1777.                 flagIndex = firstFlag
  1778.                 for b = 1 to Bones.count do
  1779.                 (
  1780.                     -- get key
  1781.                     k = ReadVQuatAnimKey file                -- read key from file
  1782.                     -- get bones
  1783.                     bone     = MaxBones[b]                    -- scene bone to transform
  1784.                     BindPose = BindPoseInfo[b]                -- for BindPose transform
  1785.                     -- get animation flags
  1786.                     flag = 0
  1787.                     if flagIndex < flagCount then flag = AnimFlags[flagIndex]
  1788.                     flagIndex = flagIndex + 1
  1789.                     -- when either scene or mesh bone is missing, skip everything (key was already read)
  1790.                     if bone == undefined then continue
  1791.  
  1792.                     local mat
  1793.                     if BindPose != undefined then
  1794.                     (
  1795.                         -- rotation
  1796.                         if (bit.and flag 2) != 0 then        -- NO_ROTATION
  1797.                         (
  1798.                             -- rotation from mesh
  1799.                             mat = BindPose.AX_RelMatrix
  1800.                         )
  1801.                         else
  1802.                         (
  1803.                             -- rotation from animation
  1804.                             q = k.Orientation
  1805.                             if ((b == 1) and not g_dontConjugateRoot) then q = conjugate q
  1806.                             mat = (q as matrix3)
  1807.                         )
  1808.                         -- translation
  1809.                         if (bit.and flag 1) != 0 then        -- NO_TRANSLATION
  1810.                         (
  1811.                             -- translation from the mesh
  1812.                             mat.row4 = BindPose.AX_RelMatrix.row4 * g_meshScale
  1813.                         )
  1814.                         else if not UseAnimTranslation[b] then
  1815.                         (
  1816.                             -- translation from the mesh
  1817.                             mat.row4 = BindPose.AX_RelMatrix.row4 * g_meshScale
  1818.                         )
  1819.                         else
  1820.                         (
  1821.                             -- translation from animation
  1822.                             mat.row4 = k.Position * g_meshScale
  1823.                         )
  1824.                     )
  1825.                     else
  1826.                     (
  1827.                         -- the BindPose object doesn't exists, use all data from the animation
  1828.                         q = k.Orientation                    -- rotation from animation
  1829.                         p = k.Position * g_meshScale        -- translation from animation
  1830.                         -- build matrix
  1831.                         if ((b == 1) and not g_dontConjugateRoot) then q = conjugate q
  1832.                         -- build matrix
  1833.                         mat = (q as matrix3)
  1834.                         mat.row4 = p
  1835.                     )
  1836.  
  1837.                     -- modify bone
  1838.                     if bone.parent != undefined then
  1839.                     (
  1840.                         bone.transform = mat * bone.parent.transform
  1841.                     )
  1842.                     else
  1843.                     (
  1844.                         bone.transform = mat
  1845.                     )
  1846.                     -- remember 1st frame
  1847.                     if (i == 1) then firstFrame[b] = bone.transform
  1848.                 )
  1849.                 -- rotate animation
  1850.                 if MaxBones[1] != undefined then
  1851.                 (
  1852.                     MaxBones[1].transform = MaxBones[1].transform * axGetRootMatrix()
  1853.                 )
  1854.             )
  1855.             -- progress bar
  1856.             progressUpdate (100.0 * i / numFrames)
  1857.             if getProgressCancel() then exit
  1858.         )
  1859.         if g_fixLooping then
  1860.         (
  1861.             -- Add extra 2 frames for correct TCB controller work.
  1862.             -- The second frame is not necessary if there is no keys after last frame
  1863.             -- (may purge all keys before animation loading instead of adding 2nd key)
  1864.             for i = 0 to 1 do
  1865.             (
  1866.                 at time (startframe + numFrames + i)
  1867.                 for b = 1 to Bones.count do
  1868.                 (
  1869.                     bone = MaxBones[b]
  1870.                     if bone != undefined then
  1871.                     (
  1872.                         bone.transform = firstFrame[b]
  1873.                     )
  1874.                 )
  1875.             )
  1876.         )
  1877.         progressEnd()
  1878.     )
  1879.  
  1880.     -- finish loading
  1881.     fclose file
  1882.  
  1883.     sliderTime = 1
  1884.     extraFrame = 0
  1885.     if g_fixLooping then extraFrame = 1
  1886.  
  1887.     if g_updateTime then
  1888.     (
  1889.         ar_start = startframe
  1890.         ar_end   = startframe + numFrames - 1 + extraFrame
  1891.     )
  1892.     else
  1893.     (
  1894.         ar_start = animationRange.start.frame
  1895.         ar_end   = animationRange.end.frame
  1896.         if animationRange.start.frame > startframe then
  1897.             ar_start = startframe
  1898.         if animationRange.end.frame < startframe + numFrames + extraFrame then
  1899.             ar_end   = startframe + numFrames - 1 + extraFrame
  1900.     )
  1901.     if (ar_end == ar_start) then ar_end = ar_end + 1 -- avoid zero-length intervals
  1902.  
  1903.     animationRange = interval ar_start ar_end
  1904.     sliderTime     = startframe
  1905. --    frameRate      = track.AnimRate
  1906.  
  1907.     if g_playAnim then playAnimation immediateReturn:true
  1908.  
  1909.     gc()
  1910. )
  1911.  
  1912.  
  1913. -------------------------------------------------------------------------------
  1914. --    User interface
  1915. -------------------------------------------------------------------------------
  1916.  
  1917. -- layout
  1918. global axRolloutList
  1919. global axRolloutStates
  1920. global g_axScrollPos
  1921.  
  1922. fn axStoreLayout roll =
  1923. (
  1924.     if axRolloutStates == undefined then axRolloutStates = #()
  1925.     for i = 1 to axRolloutList.count do
  1926.     (
  1927.         axRolloutStates[i] = axRolloutList[i].open
  1928.     )
  1929.     -- sometimes 'roll' is non-null, but it's property 'scrollPos' is inaccessible
  1930.     if roll.scrollPos != undefined then g_axScrollPos = roll.scrollPos
  1931. )
  1932.  
  1933.  
  1934. fn axRestoreLayout roll =
  1935. (
  1936.     if axRolloutStates != undefined then
  1937.     (
  1938.         for i = 1 to axRolloutList.count do
  1939.         (
  1940.             axRolloutList[i].open = axRolloutStates[i]
  1941.         )
  1942.     )
  1943.     -- when execing first time, layout will not be stored, and g_axScrollPos will be undefined
  1944.     if g_axScrollPos != undefined then roll.scrollPos = g_axScrollPos
  1945. )
  1946.  
  1947.  
  1948. global MeshFileName
  1949. global AnimFileName
  1950.  
  1951. fn axLoadAnimation index =
  1952. (
  1953.     if (index > 0) and (index <= Anims.count) then ImportPsaFile AnimFileName index
  1954. )
  1955.  
  1956.  
  1957. rollout axInfoRollout "ActorX Importer"
  1958. (
  1959.     -- copyright label
  1960.     label     Lbl1 "Version 1.35"
  1961.     label     Lbl2 "\xA9 2009-2017 Konstantin Nosov (Gildor)"
  1962.     hyperlink Lbl3 "http://www.gildor.org/" \
  1963.                     address:"http://www.gildor.org/projects/unactorx" align:#center \
  1964.                     color:black hovercolor:blue visitedcolor:black
  1965.  
  1966.     on axInfoRollout close do
  1967.     (
  1968.         format "Saving settings ...\n"
  1969.         axSerializeSettings false
  1970.         axStoreLayout axInfoRollout
  1971.     )
  1972. )
  1973.  
  1974.  
  1975. rollout axMeshImportRollout "Mesh Import"
  1976. (
  1977.     checkbox ChkSeeThru    "See-Thru Mesh" checked:g_seeThru
  1978.     checkbox ChkSkelOnly   "Load skeleton only" checked:g_skelOnly
  1979.     button   BtnImportPsk  "Import PSK ..."
  1980.  
  1981.     -- event handlers
  1982.  
  1983.     on ChkSeeThru    changed state do g_seeThru    = state
  1984.     on ChkSkelOnly   changed state do g_skelOnly   = state
  1985.  
  1986.     on BtnImportPsk pressed do
  1987.     (
  1988.         if DotNetObject == undefined then
  1989.         (
  1990.             -- older Max didn't have functionality for getMultiOpenFilenames
  1991.             local filename = getOpenFileName types:"ActorX Mesh (*.psk,*.pskx)|*.psk;*.pskx|All (*.*)|*.*|" filename:g_lastDir1
  1992.             if filename != undefined then
  1993.             (
  1994.                 MeshFileName = filename
  1995.                 g_lastDir1 = getFilenamePath MeshFileName
  1996.                 if DoesFileExist MeshFileName then ImportPskFile MeshFileName skelOnly:g_skelOnly
  1997.             )
  1998.         )
  1999.         else
  2000.         (
  2001.             local filenames = getMultiOpenFilenames types:"ActorX Mesh (*.psk,*.pskx)|*.psk;*.pskx|All (*.*)|*.*" filename:g_lastDir1
  2002.             if filenames != undefined then
  2003.             (
  2004.                 for filename in filenames do
  2005.                 (
  2006.                     MeshFileName = filename
  2007.                     g_lastDir1 = getFilenamePath MeshFileName
  2008.                     if DoesFileExist MeshFileName then ImportPskFile MeshFileName skelOnly:g_skelOnly
  2009.                 )
  2010.             )
  2011.         )
  2012.     )
  2013. )
  2014.  
  2015.  
  2016. rollout axAnimImportRollout "Animation Import"
  2017. (
  2018.     Group "Animation Import"
  2019.     (
  2020.         button   BtnImportPsa  "Import PSA ..."
  2021.         listbox  LstAnims      "Animations:"     height:13
  2022.         checkbox ChkAnimTime   "Update animation length" checked:g_updateTime
  2023.         checkbox ChkFixLooping "Fix loop animation" checked:g_fixLooping tooltip:"Append 1st keyframe to animation\ntrack for smooth loop"
  2024.         checkbox ChkPlayAnim   "Play animation" checked:g_playAnim
  2025.         checkbox ChkAtSlider   "Import at slider position" checked:g_animAtSlider
  2026.         dropdownlist LstTransMode "Translation mode" items:#("Use from AnimSet", "Force mesh translation", "Force AnimSet translation") selection:g_animTransMode
  2027.         button   BtnImportTrk  "Load track" across:2
  2028.         button   BtnImportAll  "Load all" tooltip:"Load all animations as a single track"
  2029.     )
  2030.  
  2031.     -- event handlers
  2032.  
  2033.     on BtnImportPsa pressed do
  2034.     (
  2035.         local filename = getOpenFileName types:"ActorX Animation (*.psa)|*.psa|All (*.*)|*.*|" filename:g_lastDir2
  2036.  
  2037.         if filename != undefined then
  2038.         (
  2039.             AnimFileName = filename
  2040.             g_lastDir2 = getFilenamePath AnimFileName
  2041.             if DoesFileExist AnimFileName then
  2042.             (
  2043.                 ImportPsaFile AnimFileName -1
  2044.                 LstAnims.items = for a in Anims collect (a.Name + " [" + (a.NumRawFrames as string) + "]")
  2045.             )
  2046.         )
  2047.     )
  2048.  
  2049.     on BtnImportTrk pressed       do axLoadAnimation LstAnims.selection
  2050.     on BtnImportAll pressed       do ImportPsaFile AnimFileName 1 all:true
  2051.     on LstAnims doubleClicked sel do axLoadAnimation sel
  2052.  
  2053.     on ChkAnimTime   changed state do g_updateTime    = state
  2054.     on ChkFixLooping changed state do g_fixLooping    = state
  2055.     on ChkPlayAnim   changed state do g_playAnim      = state
  2056.     on ChkAtSlider   changed state do g_animAtSlider  = state
  2057.     on LstTransMode  selected mode do g_animTransMode = mode
  2058.  
  2059.     on axAnimImportRollout open do
  2060.     (
  2061.         -- fill LstAnims
  2062.         LstAnims.items = for a in Anims collect (a.Name + " [" + (a.NumRawFrames as string) + "]")
  2063.     )
  2064. )
  2065.  
  2066.  
  2067. rollout axTexturesRollout "Materials"
  2068. (
  2069.     edittext EdTexPath     "Path to materials" text:g_texDir width:180 across:2
  2070.     button   BtnBrowseTex  "..."     align:#right height:16
  2071.     checkbox ChkTexRecurse "Look in subfolders" checked:g_texRecurse
  2072.     label    LblMissingTex "On missing texture:" across:2
  2073.     radiobuttons RadMissingTex labels:#("do nothing", "ask") default:g_texMissAction align:#left columns:1
  2074.  
  2075.     on EdTexPath    changed val do g_texDir = val
  2076.     on BtnBrowseTex pressed do
  2077.     (
  2078.         dir = getSavePath caption:"Directory for texture lookup" initialDir:g_texDir
  2079.         if dir != undefined then
  2080.         (
  2081.             g_texDir       = dir
  2082.             EdTexPath.text = dir
  2083.         )
  2084.     )
  2085.  
  2086.     on ChkTexRecurse changed state do g_texRecurse    = state
  2087.     on RadMissingTex changed state do g_texMissAction = state
  2088. )
  2089.  
  2090.  
  2091. rollout axToolsRollout "Tools"
  2092. (
  2093.     button BtnReset "Reset to defaults" width:180
  2094.  
  2095.     button BtnRestoreBindpose "Restore BindPose" width:180
  2096.     button BtnRemoveAnimation "Remove animation" width:180
  2097.     button BtnClearScene      "Clear scene"      width:180
  2098.     button BtnBatchExport     "Batch export"     width:180
  2099.     button BtnReloadScript    "Reload importer"  width:180
  2100.  
  2101.     on BtnReset pressed do
  2102.     (
  2103.         if configFile != undefined then deleteFile configFile
  2104.         axDefaultSettings()
  2105.         -- reset controls
  2106.         axShowUI()
  2107.     )
  2108.     on BtnRestoreBindpose pressed do RestoreBindpose()
  2109.     on BtnRemoveAnimation pressed do RemoveAnimation()
  2110.     on BtnClearScene      pressed do ClearMaxScene()
  2111.     on BtnBatchExport     pressed do fileIn "export_fbx.ms"
  2112.     on BtnReloadScript    pressed do
  2113.     (
  2114.         if getSourceFileName != undefined then    -- checking Max version (Max9+) ...
  2115.         (
  2116.             axStoreLayout axInfoRollout
  2117.             fileIn(getSourceFileName())
  2118.         )
  2119.     )
  2120. )
  2121.  
  2122.  
  2123. rollout axSettingsRollout "Mesh Settings"
  2124. (
  2125.     spinner SpnBoneSize  "Bone size"  range:[0.1,10,g_boneSize]     type:#float scale:0.1  align:#left  across:2
  2126.     spinner SpnMeshScale "Mesh scale" range:[0.01,1000,g_meshScale] type:#float scale:0.01 align:#right
  2127.     checkbox ChkRepBones "Reposition existing bones" checked:g_reposBones
  2128.  
  2129.     group "Mesh rotation"
  2130.     (
  2131.         spinner  SpnRY "Yaw"   range:[-180,180,g_rotY] type:#integer scale:90 fieldwidth:35 align:#left across:3
  2132.         spinner  SpnRP "Pitch" range:[-180,180,g_rotP] type:#integer scale:90 fieldwidth:35
  2133.         spinner  SpnRR "Roll"  range:[-180,180,g_rotR] type:#integer scale:90 fieldwidth:35 align:#right
  2134.         button   BtnRotMaya    "Maya" across:3
  2135.         button   BtnRotReset   "Reset"
  2136.         button   BtnRotApply   "Apply"
  2137.     )
  2138.  
  2139.     group "Mesh offset"
  2140.     (
  2141.         spinner  SpnTX "X"     range:[-10000,10000,g_transX] type:#float scale:0.01 fieldwidth:50 align:#left across:3
  2142.         spinner  SpnTY "Y"     range:[-10000,10000,g_transY] type:#float scale:0.01 fieldwidth:50
  2143.         spinner  SpnTZ "Z"     range:[-10000,10000,g_transZ] type:#float scale:0.01 fieldwidth:50 align:#right
  2144.     )
  2145.  
  2146.     -- event handlers
  2147.     on SpnBoneSize  changed val do g_boneSize  = val
  2148.     on SpnMeshScale changed val do g_meshScale = val
  2149.     on ChkRepBones  changed state do g_reposBones = state
  2150.  
  2151.     on SpnRY changed val do g_rotY = val
  2152.     on SpnRP changed val do g_rotP = val
  2153.     on SpnRR changed val do g_rotR = val
  2154.     on SpnTX changed val do g_transX = val
  2155.     on SpnTY changed val do g_transY = val
  2156.     on SpnTZ changed val do g_transZ = val
  2157.  
  2158.     on BtnRotMaya pressed do
  2159.     (
  2160.         g_rotY = SpnRY.value = -90
  2161.         g_rotP = SpnRP.value =   0
  2162.         g_rotR = SpnRR.value =  90
  2163.         RestoreBindpose()
  2164.     )
  2165.     on BtnRotReset pressed do
  2166.     (
  2167.         g_rotY = SpnRY.value = 0
  2168.         g_rotP = SpnRP.value = 0
  2169.         g_rotR = SpnRR.value = 0
  2170.         RestoreBindpose()
  2171.     )
  2172.     on BtnRotApply pressed do RestoreBindpose()
  2173. )
  2174.  
  2175.  
  2176. rollout axAdvSettingsRollout "Advanced Settings"
  2177. (
  2178.     label Lbl1                "WARNING: do not modify these settings"
  2179.     label Lbl2                "unless you know what you are doing!"
  2180.     checkbox ChkReorientBones "Reorient bones" checked:g_reorientBones
  2181.     checkbox ChkDontConjRoot  "Don't conjugate root bone" checked:g_dontConjugateRoot
  2182.  
  2183.     -- event handlers
  2184.     on ChkReorientBones changed state do g_reorientBones = state
  2185.     on ChkDontConjRoot changed state do g_dontConjugateRoot = state
  2186. )
  2187.  
  2188.  
  2189. global axImportFloater
  2190.  
  2191. fn axShowUI =
  2192. (
  2193.     -- request position of previous window, if it was already opened
  2194.     local x = 30
  2195.     local y = 100
  2196.     local w = 250
  2197.     local h = 700
  2198.     if axImportFloater != undefined then
  2199.     (
  2200.         x = axImportFloater.pos.x
  2201.         y = axImportFloater.pos.y
  2202.         w = axImportFloater.size.x
  2203.         h = axImportFloater.size.y
  2204.         -- close old window
  2205.         closeRolloutFloater axImportFloater
  2206.     )
  2207.     -- Create plugin window
  2208.     axImportFloater = newRolloutFloater "ActorX Import" w h x y                -- create a new window
  2209.  
  2210.     -- init axRolloutList
  2211.     axRolloutList = #(axInfoRollout, axMeshImportRollout, axAnimImportRollout, axTexturesRollout, axToolsRollout, axSettingsRollout, axAdvSettingsRollout)
  2212.  
  2213.     -- add controls
  2214.     for i = 1 to axRolloutList.count do
  2215.     (
  2216.         addRollout axRolloutList[i] axImportFloater
  2217.     )
  2218.  
  2219.     axRestoreLayout axInfoRollout
  2220. )
  2221.  
  2222.  
  2223. -------------------------------------------------------------------------------
  2224. --    Plugin startup
  2225. -------------------------------------------------------------------------------
  2226.  
  2227. global g_axImporterVersion
  2228.  
  2229. if (g_axImporterVersion == undefined) then
  2230. (
  2231.     -- initialize plugin
  2232.     heapSize += 33554432    -- 32 Mb; will speedup most tasks
  2233.     Anims     = #()
  2234.     g_axImporterVersion = AX_IMPORTER_VERSION
  2235.     axDefaultSettings()
  2236.     axSerializeSettings(true)
  2237.  
  2238.     if getSourceFileName != undefined then    -- checking Max version (Max9+) ...
  2239.     (
  2240.         -- Add action handler (macro script).
  2241.         -- Max will copy contents of macroScript() block to the "AppData/Local/Autodesk/3dsMax/*/ENU/usermacros".
  2242.         -- To avoid copying of entire file we're generating string which will simply execute THIS file.
  2243.         str = "macroScript GildorTools_ActorXImporter category:\"Gildor Tools\" buttontext:\"ActorX Importer\" tooltip:\"ActorX Importer\"\n" \
  2244.             + "(\n" \
  2245.             + "    fileIn \"" + getSourceFileName() + "\"\n" \
  2246.             + ")\n"
  2247.         execute str
  2248.     )
  2249. )
  2250.  
  2251.  
  2252. if (g_axImporterVersion != AX_IMPORTER_VERSION) then
  2253. (
  2254.     format "ActorX Importer has been updated while 3ds Max is running.\nReloading settings.\n"
  2255.     -- copy-paste of code above
  2256.     g_axImporterVersion = AX_IMPORTER_VERSION
  2257.     axDefaultSettings()
  2258.     axSerializeSettings(true)
  2259. )
  2260.  
  2261. axShowUI()
  2262.