FbxExporter.cs 188 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522
  1. using System.IO;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.Animations;
  5. using UnityEditor;
  6. using System.Linq;
  7. using Autodesk.Fbx;
  8. using System.Runtime.CompilerServices;
  9. using System.Runtime.Serialization;
  10. using UnityEditor.Formats.Fbx.Exporter.Visitors;
  11. using UnityEditor.Formats.Fbx.Exporter.CustomExtensions;
  12. using System.Security.Permissions;
  13. [assembly: InternalsVisibleTo("Unity.Formats.Fbx.Editor.Tests")]
  14. [assembly: InternalsVisibleTo("Unity.ProBuilder.AddOns.Editor")]
  15. namespace UnityEditor.Formats.Fbx.Exporter
  16. {
  17. /// <summary>
  18. /// If your MonoBehaviour knows about some custom geometry that
  19. /// isn't in a MeshFilter or SkinnedMeshRenderer, use
  20. /// RegisterMeshCallback to get a callback when the exporter tries
  21. /// to export your component.
  22. ///
  23. /// The callback should return true, and output the mesh you want.
  24. ///
  25. /// Return false if you don't want to drive this game object.
  26. ///
  27. /// Return true and output a null mesh if you don't want the
  28. /// exporter to output anything.
  29. /// </summary>
  30. internal delegate bool GetMeshForComponent<T>(ModelExporter exporter, T component, FbxNode fbxNode) where T : MonoBehaviour;
  31. internal delegate bool GetMeshForComponent(ModelExporter exporter, MonoBehaviour component, FbxNode fbxNode);
  32. /// <summary>
  33. /// Delegate used to convert a GameObject into a mesh.
  34. ///
  35. /// This is useful if you want to have broader control over
  36. /// the export process than the GetMeshForComponent callbacks
  37. /// provide. But it's less efficient because you'll get a callback
  38. /// on every single GameObject.
  39. /// </summary>
  40. internal delegate bool GetMeshForObject(ModelExporter exporter, GameObject gameObject, FbxNode fbxNode);
  41. [System.Serializable]
  42. internal class ModelExportException : System.Exception
  43. {
  44. public ModelExportException(){}
  45. public ModelExportException(string message)
  46. : base(message){}
  47. public ModelExportException(string message, System.Exception inner)
  48. : base(message, inner){}
  49. protected ModelExportException(SerializationInfo info, StreamingContext context)
  50. : base(info, context){}
  51. }
  52. /// <summary>
  53. /// Use the ModelExporter class to export Unity GameObjects to an FBX file.
  54. /// <para>
  55. /// Use the ExportObject and ExportObjects methods. The default export
  56. /// options are used when exporting the objects to the FBX file.
  57. /// </para>
  58. /// <para>For information on using the ModelExporter class, see <a href="../manual/devguide.html">the Developer's Guide</a>.</para>
  59. /// </summary>
  60. public sealed class ModelExporter : System.IDisposable
  61. {
  62. const string Title =
  63. "Created by FBX Exporter from Unity Technologies";
  64. const string Subject =
  65. "";
  66. const string Keywords =
  67. "Nodes Meshes Materials Textures Cameras Lights Skins Animation";
  68. const string Comments =
  69. @"";
  70. /// <summary>
  71. /// Path to the CHANGELOG file in Unity's virtual file system. Used to get the version number.
  72. /// </summary>
  73. const string ChangeLogPath = "Packages/com.unity.formats.fbx/CHANGELOG.md";
  74. // NOTE: The ellipsis at the end of the Menu Item name prevents the context
  75. // from being passed to command, thus resulting in OnContextItem()
  76. // being called only once regardless of what is selected.
  77. const string MenuItemName = "GameObject/Export To FBX...";
  78. const string TimelineClipMenuItemName = "GameObject/Export Selected Timeline Clip...";
  79. const string ProgressBarTitle = "FBX Export";
  80. const char MayaNamespaceSeparator = ':';
  81. // replace invalid chars with this one
  82. const char InvalidCharReplacement = '_';
  83. const string RegexCharStart = "[";
  84. const string RegexCharEnd = "]";
  85. internal const float UnitScaleFactor = 100f;
  86. internal const string PACKAGE_UI_NAME = "FBX Exporter";
  87. /// <summary>
  88. /// name of the scene's default camera
  89. /// </summary>
  90. private static string DefaultCamera = "";
  91. private const string SkeletonPrefix = "_Skel";
  92. private const string SkinPrefix = "_Skin";
  93. /// <summary>
  94. /// name prefix for custom properties
  95. /// </summary>
  96. const string NamePrefix = "Unity_";
  97. private static string MakeName (string basename)
  98. {
  99. return NamePrefix + basename;
  100. }
  101. /// <summary>
  102. /// Create instance of exporter.
  103. /// </summary>
  104. static ModelExporter Create ()
  105. {
  106. return new ModelExporter ();
  107. }
  108. /// <summary>
  109. /// Which components map from Unity Object to Fbx Object
  110. /// </summary>
  111. internal enum FbxNodeRelationType
  112. {
  113. NodeAttribute,
  114. Property,
  115. Material
  116. }
  117. internal static Dictionary<System.Type, KeyValuePair<System.Type,FbxNodeRelationType>> MapsToFbxObject = new Dictionary<System.Type, KeyValuePair<System.Type,FbxNodeRelationType>> ()
  118. {
  119. { typeof(Transform), new KeyValuePair<System.Type, FbxNodeRelationType>(typeof(FbxProperty), FbxNodeRelationType.Property) },
  120. { typeof(MeshFilter), new KeyValuePair<System.Type, FbxNodeRelationType>(typeof(FbxMesh), FbxNodeRelationType.NodeAttribute) },
  121. { typeof(SkinnedMeshRenderer), new KeyValuePair<System.Type, FbxNodeRelationType>(typeof(FbxMesh), FbxNodeRelationType.NodeAttribute) },
  122. { typeof(Light), new KeyValuePair<System.Type, FbxNodeRelationType>(typeof(FbxLight), FbxNodeRelationType.NodeAttribute) },
  123. { typeof(Camera), new KeyValuePair<System.Type, FbxNodeRelationType>(typeof(FbxCamera), FbxNodeRelationType.NodeAttribute) },
  124. { typeof(Material), new KeyValuePair<System.Type, FbxNodeRelationType>(typeof(FbxSurfaceMaterial), FbxNodeRelationType.Material) },
  125. };
  126. /// <summary>
  127. /// keep a map between GameObject and FbxNode for quick lookup when we export
  128. /// animation.
  129. /// </summary>
  130. Dictionary<GameObject, FbxNode> MapUnityObjectToFbxNode = new Dictionary<GameObject, FbxNode> ();
  131. /// <summary>
  132. /// keep a map between the constrained FbxNode (in Unity this is the GameObject with constraint component)
  133. /// and its FbxConstraints for quick lookup when exporting constraint animations.
  134. /// </summary>
  135. Dictionary<FbxNode, Dictionary<FbxConstraint, System.Type>> MapConstrainedObjectToConstraints = new Dictionary<FbxNode, Dictionary<FbxConstraint, System.Type>>();
  136. /// <summary>
  137. /// Map Unity material ID to FBX material object
  138. /// </summary>
  139. Dictionary<int, FbxSurfaceMaterial> MaterialMap = new Dictionary<int, FbxSurfaceMaterial> ();
  140. /// <summary>
  141. /// Map texture filename name to FBX texture object
  142. /// </summary>
  143. Dictionary<string, FbxTexture> TextureMap = new Dictionary<string, FbxTexture> ();
  144. /// <summary>
  145. /// Map the ID of a prefab to an FbxMesh (for preserving instances)
  146. /// </summary>
  147. Dictionary<int, FbxMesh> SharedMeshes = new Dictionary<int, FbxMesh> ();
  148. /// <summary>
  149. /// Map for the Name of an Object to number of objects with this name.
  150. /// Used for enforcing unique names on export.
  151. /// </summary>
  152. Dictionary<string, int> NameToIndexMap = new Dictionary<string, int> ();
  153. /// <summary>
  154. /// Map for the Material Name to number of materials with this name.
  155. /// Used for enforcing unique names on export.
  156. /// </summary>
  157. Dictionary<string, int> MaterialNameToIndexMap = new Dictionary<string, int>();
  158. /// <summary>
  159. /// Map for the Texture Name to number of textures with this name.
  160. /// Used for enforcing unique names on export.
  161. /// </summary>
  162. Dictionary<string, int> TextureNameToIndexMap = new Dictionary<string, int>();
  163. Dictionary<Mesh, FbxNode> MeshToFbxNodeMap = new Dictionary<Mesh, FbxNode>();
  164. /// <summary>
  165. /// Format for creating unique names
  166. /// </summary>
  167. const string UniqueNameFormat = "{0}_{1}";
  168. /// <summary>
  169. /// The animation fbx file format.
  170. /// </summary>
  171. const string AnimFbxFileFormat = "{0}/{1}@{2}.fbx";
  172. /// <summary>
  173. /// Gets the export settings.
  174. /// </summary>
  175. internal static ExportSettings ExportSettings {
  176. get { return ExportSettings.instance; }
  177. }
  178. internal static IExportOptions DefaultOptions {
  179. get { return new ExportModelSettingsSerialize(); }
  180. }
  181. private IExportOptions m_exportOptions;
  182. private IExportOptions ExportOptions {
  183. get {
  184. if (m_exportOptions == null) {
  185. // get default settings;
  186. m_exportOptions = DefaultOptions;
  187. }
  188. return m_exportOptions;
  189. }
  190. set { m_exportOptions = value; }
  191. }
  192. /// <summary>
  193. /// Gets the Unity default material.
  194. /// </summary>
  195. internal static Material DefaultMaterial {
  196. get {
  197. if (!s_defaultMaterial) {
  198. var obj = GameObject.CreatePrimitive (PrimitiveType.Quad);
  199. s_defaultMaterial = obj.GetComponent<Renderer> ().sharedMaterial;
  200. Object.DestroyImmediate (obj);
  201. }
  202. return s_defaultMaterial;
  203. }
  204. }
  205. static Material s_defaultMaterial = null;
  206. static Dictionary<UnityEngine.LightType, FbxLight.EType> MapLightType = new Dictionary<UnityEngine.LightType, FbxLight.EType> () {
  207. { UnityEngine.LightType.Directional, FbxLight.EType.eDirectional },
  208. { UnityEngine.LightType.Spot, FbxLight.EType.eSpot },
  209. { UnityEngine.LightType.Point, FbxLight.EType.ePoint },
  210. { UnityEngine.LightType.Area, FbxLight.EType.eArea },
  211. };
  212. /// <summary>
  213. /// Gets the version number of the FbxExporters plugin from the readme.
  214. /// </summary>
  215. internal static string GetVersionFromReadme()
  216. {
  217. if (!File.Exists (ChangeLogPath)) {
  218. Debug.LogWarning (string.Format("Could not find version number, the ChangeLog file is missing from: {0}", ChangeLogPath));
  219. return null;
  220. }
  221. try {
  222. // The standard format is:
  223. // ## [a.b.c-whatever] - yyyy-mm-dd
  224. // Another format is:
  225. // **Version**: a.b.c-whatever
  226. // we handle either one and read out the version
  227. var lines = File.ReadAllLines (ChangeLogPath);
  228. var regexes = new string [] {
  229. @"^\s*##\s*\[(.*)\]",
  230. @"^\s*\*\*Version\*\*:\s*(.*)\s*"
  231. };
  232. foreach (var line in lines) {
  233. foreach (var regex in regexes) {
  234. var match = System.Text.RegularExpressions.Regex.Match(line, regex);
  235. if (match.Success) {
  236. var version = match.Groups[1].Value;
  237. return version.Trim ();
  238. }
  239. }
  240. }
  241. // If we're here, we didn't find any match.
  242. Debug.LogWarning (string.Format("Could not find most recent version number in {0}", ChangeLogPath));
  243. return null;
  244. }
  245. catch(IOException e){
  246. Debug.LogException (e);
  247. Debug.LogWarning (string.Format("Error reading file {0} ({1})", ChangeLogPath, e));
  248. return null;
  249. }
  250. }
  251. /// <summary>
  252. /// Get a layer (to store UVs, normals, etc) on the mesh.
  253. /// If it doesn't exist yet, create it.
  254. /// </summary>
  255. internal static FbxLayer GetOrCreateLayer(FbxMesh fbxMesh, int layer = 0 /* default layer */)
  256. {
  257. int maxLayerIndex = fbxMesh.GetLayerCount() - 1;
  258. while (layer > maxLayerIndex) {
  259. // We'll have to create the layer (potentially several).
  260. // Make sure to avoid infinite loops even if there's an
  261. // FbxSdk bug.
  262. int newLayerIndex = fbxMesh.CreateLayer();
  263. if (newLayerIndex <= maxLayerIndex) {
  264. // Error!
  265. throw new ModelExportException(
  266. "Internal error: Unable to create mesh layer "
  267. + (maxLayerIndex + 1)
  268. + " on mesh " + fbxMesh.GetName ());
  269. }
  270. maxLayerIndex = newLayerIndex;
  271. }
  272. return fbxMesh.GetLayer (layer);
  273. }
  274. /// <summary>
  275. /// Export the mesh's attributes using layer 0.
  276. /// </summary>
  277. private bool ExportComponentAttributes (MeshInfo mesh, FbxMesh fbxMesh, int[] unmergedTriangles)
  278. {
  279. // return true if any attribute was exported
  280. bool exportedAttribute = false;
  281. // Set the normals on Layer 0.
  282. FbxLayer fbxLayer = GetOrCreateLayer(fbxMesh);
  283. if (mesh.HasValidNormals()) {
  284. using (var fbxLayerElement = FbxLayerElementNormal.Create (fbxMesh, "Normals")) {
  285. fbxLayerElement.SetMappingMode (FbxLayerElement.EMappingMode.eByPolygonVertex);
  286. fbxLayerElement.SetReferenceMode (FbxLayerElement.EReferenceMode.eDirect);
  287. // Add one normal per each vertex face index (3 per triangle)
  288. FbxLayerElementArray fbxElementArray = fbxLayerElement.GetDirectArray ();
  289. for (int n = 0; n < unmergedTriangles.Length; n++) {
  290. int unityTriangle = unmergedTriangles [n];
  291. fbxElementArray.Add (ConvertToRightHanded (mesh.Normals [unityTriangle]));
  292. }
  293. fbxLayer.SetNormals (fbxLayerElement);
  294. }
  295. exportedAttribute = true;
  296. }
  297. /// Set the binormals on Layer 0.
  298. if (mesh.HasValidBinormals()) {
  299. using (var fbxLayerElement = FbxLayerElementBinormal.Create (fbxMesh, "Binormals")) {
  300. fbxLayerElement.SetMappingMode (FbxLayerElement.EMappingMode.eByPolygonVertex);
  301. fbxLayerElement.SetReferenceMode (FbxLayerElement.EReferenceMode.eDirect);
  302. // Add one normal per each vertex face index (3 per triangle)
  303. FbxLayerElementArray fbxElementArray = fbxLayerElement.GetDirectArray ();
  304. for (int n = 0; n < unmergedTriangles.Length; n++) {
  305. int unityTriangle = unmergedTriangles [n];
  306. fbxElementArray.Add (ConvertToRightHanded (mesh.Binormals [unityTriangle]));
  307. }
  308. fbxLayer.SetBinormals (fbxLayerElement);
  309. }
  310. exportedAttribute = true;
  311. }
  312. /// Set the tangents on Layer 0.
  313. if (mesh.HasValidTangents()) {
  314. using (var fbxLayerElement = FbxLayerElementTangent.Create (fbxMesh, "Tangents")) {
  315. fbxLayerElement.SetMappingMode (FbxLayerElement.EMappingMode.eByPolygonVertex);
  316. fbxLayerElement.SetReferenceMode (FbxLayerElement.EReferenceMode.eDirect);
  317. // Add one normal per each vertex face index (3 per triangle)
  318. FbxLayerElementArray fbxElementArray = fbxLayerElement.GetDirectArray ();
  319. for (int n = 0; n < unmergedTriangles.Length; n++) {
  320. int unityTriangle = unmergedTriangles [n];
  321. fbxElementArray.Add (ConvertToRightHanded (
  322. new Vector3 (
  323. mesh.Tangents [unityTriangle] [0],
  324. mesh.Tangents [unityTriangle] [1],
  325. mesh.Tangents [unityTriangle] [2]
  326. )));
  327. }
  328. fbxLayer.SetTangents (fbxLayerElement);
  329. }
  330. exportedAttribute = true;
  331. }
  332. exportedAttribute |= ExportUVs (fbxMesh, mesh, unmergedTriangles);
  333. if (mesh.HasValidVertexColors()) {
  334. using (var fbxLayerElement = FbxLayerElementVertexColor.Create (fbxMesh, "VertexColors")) {
  335. fbxLayerElement.SetMappingMode (FbxLayerElement.EMappingMode.eByPolygonVertex);
  336. fbxLayerElement.SetReferenceMode (FbxLayerElement.EReferenceMode.eIndexToDirect);
  337. // set texture coordinates per vertex
  338. FbxLayerElementArray fbxElementArray = fbxLayerElement.GetDirectArray ();
  339. // (Uni-31596) only copy unique UVs into this array, and index appropriately
  340. for (int n = 0; n < mesh.VertexColors.Length; n++) {
  341. // Converting to Color from Color32, as Color32 stores the colors
  342. // as ints between 0-255, while FbxColor and Color
  343. // use doubles between 0-1
  344. Color color = mesh.VertexColors [n];
  345. fbxElementArray.Add (new FbxColor (color.r,
  346. color.g,
  347. color.b,
  348. color.a));
  349. }
  350. // For each face index, point to a texture uv
  351. FbxLayerElementArray fbxIndexArray = fbxLayerElement.GetIndexArray ();
  352. fbxIndexArray.SetCount (unmergedTriangles.Length);
  353. for (int i = 0; i < unmergedTriangles.Length; i++) {
  354. fbxIndexArray.SetAt (i, unmergedTriangles [i]);
  355. }
  356. fbxLayer.SetVertexColors (fbxLayerElement);
  357. }
  358. exportedAttribute = true;
  359. }
  360. return exportedAttribute;
  361. }
  362. /// <summary>
  363. /// Unity has up to 4 uv sets per mesh. Export all the ones that exist.
  364. /// </summary>
  365. /// <param name="fbxMesh">Fbx mesh.</param>
  366. /// <param name="mesh">Mesh.</param>
  367. /// <param name="unmergedTriangles">Unmerged triangles.</param>
  368. private static bool ExportUVs(FbxMesh fbxMesh, MeshInfo mesh, int[] unmergedTriangles)
  369. {
  370. Vector2[][] uvs = new Vector2[][] {
  371. mesh.UV,
  372. mesh.mesh.uv2,
  373. mesh.mesh.uv3,
  374. mesh.mesh.uv4
  375. };
  376. int k = 0;
  377. for (int i = 0; i < uvs.Length; i++) {
  378. if (uvs [i] == null || uvs [i].Length == 0) {
  379. continue; // don't have these UV's, so skip
  380. }
  381. FbxLayer fbxLayer = GetOrCreateLayer (fbxMesh, k);
  382. using (var fbxLayerElement = FbxLayerElementUV.Create (fbxMesh, "UVSet" + i))
  383. {
  384. fbxLayerElement.SetMappingMode (FbxLayerElement.EMappingMode.eByPolygonVertex);
  385. fbxLayerElement.SetReferenceMode (FbxLayerElement.EReferenceMode.eIndexToDirect);
  386. // set texture coordinates per vertex
  387. FbxLayerElementArray fbxElementArray = fbxLayerElement.GetDirectArray ();
  388. // (Uni-31596) only copy unique UVs into this array, and index appropriately
  389. for (int n = 0; n < uvs[i].Length; n++) {
  390. fbxElementArray.Add (new FbxVector2 (uvs[i] [n] [0],
  391. uvs[i] [n] [1]));
  392. }
  393. // For each face index, point to a texture uv
  394. FbxLayerElementArray fbxIndexArray = fbxLayerElement.GetIndexArray ();
  395. fbxIndexArray.SetCount (unmergedTriangles.Length);
  396. for(int j = 0; j < unmergedTriangles.Length; j++){
  397. fbxIndexArray.SetAt (j, unmergedTriangles [j]);
  398. }
  399. fbxLayer.SetUVs (fbxLayerElement, FbxLayerElement.EType.eTextureDiffuse);
  400. }
  401. k++;
  402. }
  403. // if we incremented k, then at least on set of UV's were exported
  404. return k > 0;
  405. }
  406. /// <summary>
  407. /// Export the mesh's blend shapes.
  408. /// </summary>
  409. private bool ExportBlendShapes(MeshInfo mesh, FbxMesh fbxMesh, FbxScene fbxScene, int[] unmergedTriangles)
  410. {
  411. var umesh = mesh.mesh;
  412. if (umesh.blendShapeCount == 0)
  413. return false;
  414. var fbxBlendShape = FbxBlendShape.Create(fbxScene, umesh.name + "_BlendShape");
  415. fbxMesh.AddDeformer(fbxBlendShape);
  416. var numVertices = umesh.vertexCount;
  417. var basePoints = umesh.vertices;
  418. var baseNormals = umesh.normals;
  419. var baseTangents = umesh.tangents;
  420. var deltaPoints = new Vector3[numVertices];
  421. var deltaNormals = new Vector3[numVertices];
  422. var deltaTangents = new Vector3[numVertices];
  423. for (int bi = 0; bi < umesh.blendShapeCount; ++bi)
  424. {
  425. var bsName = umesh.GetBlendShapeName(bi);
  426. var numFrames = umesh.GetBlendShapeFrameCount(bi);
  427. var fbxChannel = FbxBlendShapeChannel.Create(fbxScene, bsName);
  428. fbxBlendShape.AddBlendShapeChannel(fbxChannel);
  429. for (int fi = 0; fi < numFrames; ++fi)
  430. {
  431. var weight = umesh.GetBlendShapeFrameWeight(bi, fi);
  432. umesh.GetBlendShapeFrameVertices(bi, fi, deltaPoints, deltaNormals, deltaTangents);
  433. var fbxShapeName = bsName;
  434. if (numFrames > 1)
  435. {
  436. fbxShapeName += "_" + fi;
  437. }
  438. var fbxShape = FbxShape.Create(fbxScene, fbxShapeName);
  439. fbxChannel.AddTargetShape(fbxShape, weight);
  440. // control points
  441. fbxShape.InitControlPoints(ControlPointToIndex.Count());
  442. for (int vi = 0; vi < numVertices; ++vi)
  443. {
  444. int ni = ControlPointToIndex[basePoints[vi]];
  445. var v = basePoints[vi] + deltaPoints[vi];
  446. fbxShape.SetControlPointAt(ConvertToRightHanded(v, UnitScaleFactor), ni);
  447. }
  448. // normals
  449. if (mesh.HasValidNormals())
  450. {
  451. var elemNormals = fbxShape.CreateElementNormal();
  452. elemNormals.SetMappingMode(FbxLayerElement.EMappingMode.eByPolygonVertex);
  453. elemNormals.SetReferenceMode(FbxLayerElement.EReferenceMode.eDirect);
  454. var dstNormals = elemNormals.GetDirectArray();
  455. dstNormals.SetCount(unmergedTriangles.Length);
  456. for (int ii = 0; ii < unmergedTriangles.Length; ++ii)
  457. {
  458. int vi = unmergedTriangles[ii];
  459. var n = baseNormals[vi] + deltaNormals[vi];
  460. dstNormals.SetAt(ii, ConvertToRightHanded(n));
  461. }
  462. }
  463. // tangents
  464. if (mesh.HasValidTangents())
  465. {
  466. var elemTangents = fbxShape.CreateElementTangent();
  467. elemTangents.SetMappingMode(FbxLayerElement.EMappingMode.eByPolygonVertex);
  468. elemTangents.SetReferenceMode(FbxLayerElement.EReferenceMode.eDirect);
  469. var dstTangents = elemTangents.GetDirectArray();
  470. dstTangents.SetCount(unmergedTriangles.Length);
  471. for (int ii = 0; ii < unmergedTriangles.Length; ++ii)
  472. {
  473. int vi = unmergedTriangles[ii];
  474. var t = (Vector3)baseTangents[vi] + deltaTangents[vi];
  475. dstTangents.SetAt(ii, ConvertToRightHanded(t));
  476. }
  477. }
  478. }
  479. }
  480. return true;
  481. }
  482. /// <summary>
  483. /// Takes in a left-handed UnityEngine.Vector3 denoting a normal,
  484. /// returns a right-handed FbxVector4.
  485. ///
  486. /// Unity is left-handed, Maya and Max are right-handed.
  487. /// The FbxSdk conversion routines can't handle changing handedness.
  488. ///
  489. /// Remember you also need to flip the winding order on your polygons.
  490. /// </summary>
  491. internal static FbxVector4 ConvertToRightHanded(Vector3 leftHandedVector, float unitScale = 1f)
  492. {
  493. // negating the x component of the vector converts it from left to right handed coordinates
  494. return unitScale * new FbxVector4 (
  495. -leftHandedVector[0],
  496. leftHandedVector[1],
  497. leftHandedVector[2]);
  498. }
  499. /// <summary>
  500. /// Exports a texture from Unity to FBX.
  501. /// The texture must be a property on the unityMaterial; it gets
  502. /// linked to the FBX via a property on the fbxMaterial.
  503. ///
  504. /// The texture file must be a file on disk; it is not embedded within the FBX.
  505. /// </summary>
  506. /// <param name="unityMaterial">Unity material.</param>
  507. /// <param name="unityPropName">Unity property name, e.g. "_MainTex".</param>
  508. /// <param name="fbxMaterial">Fbx material.</param>
  509. /// <param name="fbxPropName">Fbx property name, e.g. <c>FbxSurfaceMaterial.sDiffuse</c>.</param>
  510. internal bool ExportTexture (Material unityMaterial, string unityPropName,
  511. FbxSurfaceMaterial fbxMaterial, string fbxPropName)
  512. {
  513. if (!unityMaterial) {
  514. return false;
  515. }
  516. // Get the texture on this property, if any.
  517. if (!unityMaterial.HasProperty (unityPropName)) {
  518. return false;
  519. }
  520. var unityTexture = unityMaterial.GetTexture (unityPropName);
  521. if (!unityTexture) {
  522. return false;
  523. }
  524. // Find its filename
  525. var textureSourceFullPath = AssetDatabase.GetAssetPath(unityTexture);
  526. if (string.IsNullOrEmpty(textureSourceFullPath)) {
  527. return false;
  528. }
  529. // get absolute filepath to texture
  530. textureSourceFullPath = Path.GetFullPath (textureSourceFullPath);
  531. if (Verbose) {
  532. Debug.Log (string.Format ("{2}.{1} setting texture path {0}", textureSourceFullPath, fbxPropName, fbxMaterial.GetName ()));
  533. }
  534. // Find the corresponding property on the fbx material.
  535. var fbxMaterialProperty = fbxMaterial.FindProperty (fbxPropName);
  536. if (fbxMaterialProperty == null || !fbxMaterialProperty.IsValid ()) {
  537. Debug.Log ("property not found");
  538. return false;
  539. }
  540. // Find or create an fbx texture and link it up to the fbx material.
  541. if (!TextureMap.ContainsKey (textureSourceFullPath)) {
  542. var textureName = GetUniqueTextureName(fbxPropName + "_Texture");
  543. var fbxTexture = FbxFileTexture.Create (fbxMaterial, textureName);
  544. fbxTexture.SetFileName (textureSourceFullPath);
  545. fbxTexture.SetTextureUse (FbxTexture.ETextureUse.eStandard);
  546. fbxTexture.SetMappingType (FbxTexture.EMappingType.eUV);
  547. TextureMap.Add (textureSourceFullPath, fbxTexture);
  548. }
  549. TextureMap [textureSourceFullPath].ConnectDstProperty (fbxMaterialProperty);
  550. return true;
  551. }
  552. /// <summary>
  553. /// Get the color of a material, or grey if we can't find it.
  554. /// </summary>
  555. internal FbxDouble3 GetMaterialColor (Material unityMaterial, string unityPropName, float defaultValue = 1)
  556. {
  557. if (!unityMaterial) {
  558. return new FbxDouble3(defaultValue);
  559. }
  560. if (!unityMaterial.HasProperty (unityPropName)) {
  561. return new FbxDouble3(defaultValue);
  562. }
  563. var unityColor = unityMaterial.GetColor (unityPropName);
  564. return new FbxDouble3 (unityColor.r, unityColor.g, unityColor.b);
  565. }
  566. /// <summary>
  567. /// Export (and map) a Unity PBS material to FBX classic material
  568. /// </summary>
  569. internal bool ExportMaterial (Material unityMaterial, FbxScene fbxScene, FbxNode fbxNode)
  570. {
  571. if (!unityMaterial) {
  572. unityMaterial = DefaultMaterial;
  573. }
  574. var unityID = unityMaterial.GetInstanceID();
  575. FbxSurfaceMaterial mappedMaterial;
  576. if (MaterialMap.TryGetValue (unityID, out mappedMaterial)) {
  577. fbxNode.AddMaterial (mappedMaterial);
  578. return true;
  579. }
  580. var unityName = unityMaterial.name;
  581. var fbxName = ExportOptions.UseMayaCompatibleNames
  582. ? ConvertToMayaCompatibleName(unityName) : unityName;
  583. fbxName = GetUniqueMaterialName(fbxName);
  584. if (Verbose) {
  585. if (unityName != fbxName) {
  586. Debug.Log (string.Format ("exporting material {0} as {1}", unityName, fbxName));
  587. } else {
  588. Debug.Log(string.Format("exporting material {0}", unityName));
  589. }
  590. }
  591. // We'll export either Phong or Lambert. Phong if it calls
  592. // itself specular, Lambert otherwise.
  593. var shader = unityMaterial.shader;
  594. bool specular = shader.name.ToLower ().Contains ("specular");
  595. var fbxMaterial = specular
  596. ? FbxSurfacePhong.Create (fbxScene, fbxName)
  597. : FbxSurfaceLambert.Create (fbxScene, fbxName);
  598. // Copy the flat colours over from Unity standard materials to FBX.
  599. fbxMaterial.Diffuse.Set (GetMaterialColor (unityMaterial, "_Color"));
  600. fbxMaterial.Emissive.Set (GetMaterialColor (unityMaterial, "_EmissionColor", 0));
  601. fbxMaterial.Ambient.Set (new FbxDouble3 ());
  602. fbxMaterial.BumpFactor.Set (unityMaterial.HasProperty ("_BumpScale") ? unityMaterial.GetFloat ("_BumpScale") : 0);
  603. if (specular) {
  604. (fbxMaterial as FbxSurfacePhong).Specular.Set (GetMaterialColor (unityMaterial, "_SpecColor"));
  605. }
  606. // Export the textures from Unity standard materials to FBX.
  607. ExportTexture (unityMaterial, "_MainTex", fbxMaterial, FbxSurfaceMaterial.sDiffuse);
  608. ExportTexture (unityMaterial, "_EmissionMap", fbxMaterial, FbxSurfaceMaterial.sEmissive);
  609. ExportTexture (unityMaterial, "_BumpMap", fbxMaterial, FbxSurfaceMaterial.sNormalMap);
  610. if (specular) {
  611. ExportTexture (unityMaterial, "_SpecGlosMap", fbxMaterial, FbxSurfaceMaterial.sSpecular);
  612. }
  613. MaterialMap.Add (unityID, fbxMaterial);
  614. fbxNode.AddMaterial (fbxMaterial);
  615. return true;
  616. }
  617. /// <summary>
  618. /// Sets up the material to polygon mapping for fbxMesh.
  619. /// To determine which part of the mesh uses which material, look at the submeshes
  620. /// and which polygons they represent.
  621. /// Assuming equal number of materials as submeshes, and that they are in the same order.
  622. /// (i.e. submesh 1 uses material 1)
  623. /// </summary>
  624. /// <param name="fbxMesh">Fbx mesh.</param>
  625. /// <param name="mesh">Mesh.</param>
  626. /// <param name="materials">Materials.</param>
  627. private void AssignLayerElementMaterial(FbxMesh fbxMesh, Mesh mesh, int materialCount)
  628. {
  629. // Add FbxLayerElementMaterial to layer 0 of the node
  630. FbxLayer fbxLayer = fbxMesh.GetLayer (0 /* default layer */);
  631. if (fbxLayer == null) {
  632. fbxMesh.CreateLayer ();
  633. fbxLayer = fbxMesh.GetLayer (0 /* default layer */);
  634. }
  635. using (var fbxLayerElement = FbxLayerElementMaterial.Create (fbxMesh, "Material")) {
  636. // if there is only one material then set everything to that material
  637. if (materialCount == 1) {
  638. fbxLayerElement.SetMappingMode (FbxLayerElement.EMappingMode.eAllSame);
  639. fbxLayerElement.SetReferenceMode (FbxLayerElement.EReferenceMode.eIndexToDirect);
  640. FbxLayerElementArray fbxElementArray = fbxLayerElement.GetIndexArray ();
  641. fbxElementArray.Add (0);
  642. } else {
  643. fbxLayerElement.SetMappingMode (FbxLayerElement.EMappingMode.eByPolygon);
  644. fbxLayerElement.SetReferenceMode (FbxLayerElement.EReferenceMode.eIndexToDirect);
  645. FbxLayerElementArray fbxElementArray = fbxLayerElement.GetIndexArray ();
  646. for (int subMeshIndex = 0; subMeshIndex < mesh.subMeshCount; subMeshIndex++) {
  647. var topology = mesh.GetTopology (subMeshIndex);
  648. int polySize;
  649. switch (topology) {
  650. case MeshTopology.Triangles:
  651. polySize = 3;
  652. break;
  653. case MeshTopology.Quads:
  654. polySize = 4;
  655. break;
  656. case MeshTopology.Lines:
  657. throw new System.NotImplementedException();
  658. case MeshTopology.Points:
  659. throw new System.NotImplementedException();
  660. case MeshTopology.LineStrip:
  661. throw new System.NotImplementedException();
  662. default:
  663. throw new System.NotImplementedException();
  664. }
  665. // Specify the material index for each polygon.
  666. // Material index should match subMeshIndex.
  667. var indices = mesh.GetIndices(subMeshIndex);
  668. for (int j = 0, n = indices.Length / polySize; j < n; j++) {
  669. fbxElementArray.Add(subMeshIndex);
  670. }
  671. }
  672. }
  673. fbxLayer.SetMaterials (fbxLayerElement);
  674. }
  675. }
  676. /// <summary>
  677. /// Exports a unity mesh and attaches it to the node as an FbxMesh.
  678. ///
  679. /// Able to export materials per sub-mesh as well (by default, exports with the default material).
  680. ///
  681. /// Use fbxNode.GetMesh() to access the exported mesh.
  682. /// </summary>
  683. internal bool ExportMesh (Mesh mesh, FbxNode fbxNode, Material[] materials = null)
  684. {
  685. var meshInfo = new MeshInfo(mesh, materials);
  686. return ExportMesh(meshInfo, fbxNode);
  687. }
  688. /// <summary>
  689. /// Keeps track of the index of each point in the exported vertex array.
  690. /// </summary>
  691. private Dictionary<Vector3, int> ControlPointToIndex = new Dictionary<Vector3, int> ();
  692. /// <summary>
  693. /// Exports a unity mesh and attaches it to the node as an FbxMesh.
  694. /// </summary>
  695. bool ExportMesh (MeshInfo meshInfo, FbxNode fbxNode)
  696. {
  697. if (!meshInfo.IsValid) {
  698. return false;
  699. }
  700. NumMeshes++;
  701. NumTriangles += meshInfo.Triangles.Length / 3;
  702. // create the mesh structure.
  703. var fbxScene = fbxNode.GetScene();
  704. FbxMesh fbxMesh = FbxMesh.Create (fbxScene, "Scene");
  705. // Create control points.
  706. ControlPointToIndex.Clear();
  707. {
  708. var vertices = meshInfo.Vertices;
  709. for (int v = 0, n = meshInfo.VertexCount; v < n; v++) {
  710. if (ControlPointToIndex.ContainsKey (vertices [v])) {
  711. continue;
  712. }
  713. ControlPointToIndex [vertices [v]] = ControlPointToIndex.Count();
  714. }
  715. fbxMesh.InitControlPoints (ControlPointToIndex.Count());
  716. foreach (var kvp in ControlPointToIndex) {
  717. var controlPoint = kvp.Key;
  718. var index = kvp.Value;
  719. fbxMesh.SetControlPointAt (ConvertToRightHanded(controlPoint, UnitScaleFactor), index);
  720. }
  721. }
  722. var unmergedPolygons = new List<int> ();
  723. var mesh = meshInfo.mesh;
  724. for (int s = 0; s < mesh.subMeshCount; s++) {
  725. var topology = mesh.GetTopology (s);
  726. var indices = mesh.GetIndices (s);
  727. int polySize;
  728. int[] vertOrder;
  729. switch (topology) {
  730. case MeshTopology.Triangles:
  731. polySize = 3;
  732. // flip winding order so that Maya and Unity import it properly
  733. vertOrder = new int[] { 0, 2, 1 };
  734. break;
  735. case MeshTopology.Quads:
  736. polySize = 4;
  737. // flip winding order so that Maya and Unity import it properly
  738. vertOrder = new int[] { 0, 3, 2, 1 };
  739. break;
  740. case MeshTopology.Lines:
  741. throw new System.NotImplementedException();
  742. case MeshTopology.Points:
  743. throw new System.NotImplementedException();
  744. case MeshTopology.LineStrip:
  745. throw new System.NotImplementedException();
  746. default:
  747. throw new System.NotImplementedException();
  748. }
  749. for (int f = 0; f < indices.Length / polySize; f++) {
  750. fbxMesh.BeginPolygon ();
  751. foreach (int val in vertOrder) {
  752. int polyVert = indices [polySize * f + val];
  753. // Save the polygon order (without merging vertices) so we
  754. // properly export UVs, normals, binormals, etc.
  755. unmergedPolygons.Add(polyVert);
  756. polyVert = ControlPointToIndex [meshInfo.Vertices [polyVert]];
  757. fbxMesh.AddPolygon (polyVert);
  758. }
  759. fbxMesh.EndPolygon ();
  760. }
  761. }
  762. // Set up materials per submesh.
  763. foreach (var mat in meshInfo.Materials) {
  764. ExportMaterial (mat, fbxScene, fbxNode);
  765. }
  766. AssignLayerElementMaterial (fbxMesh, meshInfo.mesh, meshInfo.Materials.Length);
  767. // Set up normals, etc.
  768. ExportComponentAttributes (meshInfo, fbxMesh, unmergedPolygons.ToArray());
  769. // Set up blend shapes.
  770. ExportBlendShapes(meshInfo, fbxMesh, fbxScene, unmergedPolygons.ToArray());
  771. // set the fbxNode containing the mesh
  772. fbxNode.SetNodeAttribute (fbxMesh);
  773. fbxNode.SetShadingMode (FbxNode.EShadingMode.eWireFrame);
  774. return true;
  775. }
  776. /// <summary>
  777. /// Export GameObject as a skinned mesh with material, bones, a skin and, a bind pose.
  778. /// </summary>
  779. [SecurityPermission(SecurityAction.LinkDemand)]
  780. private bool ExportSkinnedMesh (GameObject unityGo, FbxScene fbxScene, FbxNode fbxNode)
  781. {
  782. if(!unityGo || fbxNode == null)
  783. {
  784. return false;
  785. }
  786. SkinnedMeshRenderer unitySkin
  787. = unityGo.GetComponent<SkinnedMeshRenderer> ();
  788. if (unitySkin == null) {
  789. return false;
  790. }
  791. var mesh = unitySkin.sharedMesh;
  792. if (!mesh) {
  793. return false;
  794. }
  795. if (Verbose)
  796. Debug.Log (string.Format ("exporting {0} {1}", "Skin", fbxNode.GetName ()));
  797. var meshInfo = new MeshInfo(unitySkin.sharedMesh, unitySkin.sharedMaterials);
  798. FbxMesh fbxMesh = null;
  799. if (ExportMesh(meshInfo, fbxNode))
  800. {
  801. fbxMesh = fbxNode.GetMesh();
  802. }
  803. if (fbxMesh == null)
  804. {
  805. Debug.LogError("Could not find mesh");
  806. return false;
  807. }
  808. Dictionary<SkinnedMeshRenderer, Transform[]> skinnedMeshToBonesMap;
  809. // export skeleton
  810. if (ExportSkeleton (unitySkin, fbxScene, out skinnedMeshToBonesMap)) {
  811. // bind mesh to skeleton
  812. ExportSkin (unitySkin, meshInfo, fbxScene, fbxMesh, fbxNode);
  813. // add bind pose
  814. ExportBindPose (unitySkin, fbxNode, fbxScene, skinnedMeshToBonesMap);
  815. // now that the skin and bindpose are set, make sure that each of the bones
  816. // is set to its original position
  817. var bones = unitySkin.bones;
  818. foreach (var bone in bones)
  819. {
  820. // ignore null bones
  821. if (bone != null)
  822. {
  823. var fbxBone = MapUnityObjectToFbxNode[bone.gameObject];
  824. ExportTransform(bone, fbxBone, newCenter: Vector3.zero, TransformExportType.Local);
  825. // Cancel out the pre-rotation from the exported rotation
  826. // Get prerotation
  827. var fbxPreRotationEuler = fbxBone.GetPreRotation(FbxNode.EPivotSet.eSourcePivot);
  828. // Convert the prerotation to a Quaternion
  829. var fbxPreRotationQuaternion = EulerToQuaternion(fbxPreRotationEuler);
  830. // Inverse of the prerotation
  831. fbxPreRotationQuaternion.Inverse();
  832. // Multiply LclRotation by pre-rotation inverse to get the LclRotation without pre-rotation applied
  833. var finalLclRotationQuat = fbxPreRotationQuaternion * EulerToQuaternion(new FbxVector4(fbxBone.LclRotation.Get()));
  834. // Convert to Euler without axis conversion (Pre-rotation and LclRotation were already in Maya axis)
  835. // and update LclRotation
  836. fbxBone.LclRotation.Set(ToFbxDouble3(QuaternionToEuler(finalLclRotationQuat)));
  837. }
  838. else
  839. {
  840. Debug.Log("Warning: One or more bones are null. Skeleton may not export correctly.");
  841. }
  842. }
  843. }
  844. return true;
  845. }
  846. /// <summary>
  847. /// Gets the bind pose for the Unity bone.
  848. /// </summary>
  849. /// <returns>The bind pose.</returns>
  850. /// <param name="unityBone">Unity bone.</param>
  851. /// <param name="bindPoses">Bind poses.</param>
  852. /// <param name="boneDict">Dictionary of bone to index.</param>
  853. /// <param name="skinnedMesh">Skinned mesh.</param>
  854. private Matrix4x4 GetBindPose(
  855. Transform unityBone, Matrix4x4[] bindPoses,
  856. Dictionary<Transform, int> boneDict, SkinnedMeshRenderer skinnedMesh
  857. ){
  858. Matrix4x4 bindPose;
  859. int index;
  860. if (boneDict.TryGetValue (unityBone, out index)) {
  861. bindPose = bindPoses [index];
  862. } else {
  863. bindPose = unityBone.worldToLocalMatrix * skinnedMesh.transform.localToWorldMatrix;
  864. }
  865. return bindPose;
  866. }
  867. /// <summary>
  868. /// Export bones of skinned mesh, if this is a skinned mesh with
  869. /// bones and bind poses.
  870. /// </summary>
  871. [SecurityPermission(SecurityAction.LinkDemand)]
  872. private bool ExportSkeleton (SkinnedMeshRenderer skinnedMesh, FbxScene fbxScene, out Dictionary<SkinnedMeshRenderer, Transform[]> skinnedMeshToBonesMap)
  873. {
  874. skinnedMeshToBonesMap = new Dictionary<SkinnedMeshRenderer, Transform[]> ();
  875. if (!skinnedMesh) {
  876. return false;
  877. }
  878. var bones = skinnedMesh.bones;
  879. if (bones == null || bones.Length == 0) {
  880. return false;
  881. }
  882. var mesh = skinnedMesh.sharedMesh;
  883. if (!mesh) {
  884. return false;
  885. }
  886. var bindPoses = mesh.bindposes;
  887. if (bindPoses == null || bindPoses.Length != bones.Length) {
  888. return false;
  889. }
  890. // Two steps:
  891. // 0. Set up the map from bone to index.
  892. // 1. Set the transforms.
  893. // Step 0: map transform to index so we can look up index by bone.
  894. Dictionary<Transform, int> index = new Dictionary<Transform, int>();
  895. for (int boneIndex = 0; boneIndex < bones.Length; boneIndex++) {
  896. Transform unityBoneTransform = bones [boneIndex];
  897. // ignore null bones
  898. if (unityBoneTransform != null)
  899. {
  900. index[unityBoneTransform] = boneIndex;
  901. }
  902. }
  903. skinnedMeshToBonesMap.Add (skinnedMesh, bones);
  904. // Step 1: Set transforms
  905. var boneInfo = new SkinnedMeshBoneInfo (skinnedMesh, index);
  906. foreach (var bone in bones) {
  907. // ignore null bones
  908. if (bone != null)
  909. {
  910. var fbxBone = MapUnityObjectToFbxNode[bone.gameObject];
  911. ExportBoneTransform(fbxBone, fbxScene, bone, boneInfo);
  912. }
  913. }
  914. return true;
  915. }
  916. /// <summary>
  917. /// Export binding of mesh to skeleton
  918. /// </summary>
  919. private bool ExportSkin (SkinnedMeshRenderer skinnedMesh,
  920. MeshInfo meshInfo, FbxScene fbxScene, FbxMesh fbxMesh,
  921. FbxNode fbxRootNode)
  922. {
  923. FbxSkin fbxSkin = FbxSkin.Create (fbxScene, (skinnedMesh.name + SkinPrefix));
  924. FbxAMatrix fbxMeshMatrix = fbxRootNode.EvaluateGlobalTransform ();
  925. // keep track of the bone index -> fbx cluster mapping, so that we can add the bone weights afterwards
  926. Dictionary<int, FbxCluster> boneCluster = new Dictionary<int, FbxCluster> ();
  927. for(int i = 0; i < skinnedMesh.bones.Length; i++) {
  928. // ignore null bones
  929. if (skinnedMesh.bones[i] != null)
  930. {
  931. FbxNode fbxBoneNode = MapUnityObjectToFbxNode[skinnedMesh.bones[i].gameObject];
  932. // Create the deforming cluster
  933. FbxCluster fbxCluster = FbxCluster.Create(fbxScene, "BoneWeightCluster");
  934. fbxCluster.SetLink(fbxBoneNode);
  935. fbxCluster.SetLinkMode(FbxCluster.ELinkMode.eNormalize);
  936. boneCluster.Add(i, fbxCluster);
  937. // set the Transform and TransformLink matrix
  938. fbxCluster.SetTransformMatrix(fbxMeshMatrix);
  939. FbxAMatrix fbxLinkMatrix = fbxBoneNode.EvaluateGlobalTransform();
  940. fbxCluster.SetTransformLinkMatrix(fbxLinkMatrix);
  941. // add the cluster to the skin
  942. fbxSkin.AddCluster(fbxCluster);
  943. }
  944. }
  945. // set the vertex weights for each bone
  946. SetVertexWeights(meshInfo, boneCluster);
  947. // Add the skin to the mesh after the clusters have been added
  948. fbxMesh.AddDeformer (fbxSkin);
  949. return true;
  950. }
  951. /// <summary>
  952. /// set vertex weights in cluster
  953. /// </summary>
  954. private void SetVertexWeights (MeshInfo meshInfo, Dictionary<int, FbxCluster> boneCluster)
  955. {
  956. HashSet<int> visitedVertices = new HashSet<int> ();
  957. // set the vertex weights for each bone
  958. for (int i = 0; i < meshInfo.BoneWeights.Length; i++) {
  959. var actualIndex = ControlPointToIndex [meshInfo.Vertices [i]];
  960. if (visitedVertices.Contains (actualIndex)) {
  961. continue;
  962. }
  963. visitedVertices.Add (actualIndex);
  964. var boneWeights = meshInfo.BoneWeights;
  965. int[] indices = {
  966. boneWeights [i].boneIndex0,
  967. boneWeights [i].boneIndex1,
  968. boneWeights [i].boneIndex2,
  969. boneWeights [i].boneIndex3
  970. };
  971. float[] weights = {
  972. boneWeights [i].weight0,
  973. boneWeights [i].weight1,
  974. boneWeights [i].weight2,
  975. boneWeights [i].weight3
  976. };
  977. for (int j = 0; j < indices.Length; j++) {
  978. if (weights [j] <= 0) {
  979. continue;
  980. }
  981. if (!boneCluster.ContainsKey (indices [j])) {
  982. continue;
  983. }
  984. // add vertex and weighting on vertex to this bone's cluster
  985. boneCluster [indices [j]].AddControlPointIndex (actualIndex, weights [j]);
  986. }
  987. }
  988. }
  989. /// <summary>
  990. /// Export bind pose of mesh to skeleton
  991. /// </summary>
  992. private bool ExportBindPose (SkinnedMeshRenderer skinnedMesh, FbxNode fbxMeshNode,
  993. FbxScene fbxScene, Dictionary<SkinnedMeshRenderer, Transform[]> skinnedMeshToBonesMap)
  994. {
  995. if (fbxMeshNode == null || skinnedMeshToBonesMap == null || fbxScene == null)
  996. {
  997. return false;
  998. }
  999. FbxPose fbxPose = FbxPose.Create(fbxScene, fbxMeshNode.GetName());
  1000. // set as bind pose
  1001. fbxPose.SetIsBindPose (true);
  1002. // assume each bone node has one weighted vertex cluster
  1003. Transform[] bones;
  1004. if (!skinnedMeshToBonesMap.TryGetValue (skinnedMesh, out bones)) {
  1005. return false;
  1006. }
  1007. for (int i = 0; i < bones.Length; i++) {
  1008. // ignore null bones
  1009. if (bones[i] != null)
  1010. {
  1011. FbxNode fbxBoneNode = MapUnityObjectToFbxNode[bones[i].gameObject];
  1012. // EvaluateGlobalTransform returns an FbxAMatrix (affine matrix)
  1013. // which has to be converted to an FbxMatrix so that it can be passed to fbxPose.Add().
  1014. // The hierarchy for FbxMatrix and FbxAMatrix is as follows:
  1015. //
  1016. // FbxDouble4x4
  1017. // / \
  1018. // FbxMatrix FbxAMatrix
  1019. //
  1020. // Therefore we can't convert directly from FbxAMatrix to FbxMatrix,
  1021. // however FbxMatrix has a constructor that takes an FbxAMatrix.
  1022. FbxMatrix fbxBindMatrix = new FbxMatrix(fbxBoneNode.EvaluateGlobalTransform());
  1023. fbxPose.Add(fbxBoneNode, fbxBindMatrix);
  1024. }
  1025. }
  1026. fbxPose.Add (fbxMeshNode, new FbxMatrix (fbxMeshNode.EvaluateGlobalTransform ()));
  1027. // add the pose to the scene
  1028. fbxScene.AddPose (fbxPose);
  1029. return true;
  1030. }
  1031. /// <summary>
  1032. /// Takes a Quaternion and returns a Euler with XYZ rotation order.
  1033. /// Also converts from left (Unity) to righthanded (Maya) coordinates.
  1034. ///
  1035. /// Note: Cannot simply use the FbxQuaternion.DecomposeSphericalXYZ()
  1036. /// function as this returns the angle in spherical coordinates
  1037. /// instead of Euler angles, which Maya does not import properly.
  1038. /// </summary>
  1039. /// <returns>Euler with XYZ rotation order.</returns>
  1040. internal static FbxDouble3 ConvertQuaternionToXYZEuler(Quaternion q)
  1041. {
  1042. FbxQuaternion quat = new FbxQuaternion (q.x, q.y, q.z, q.w);
  1043. FbxAMatrix m = new FbxAMatrix ();
  1044. m.SetQ (quat);
  1045. var vector4 = m.GetR ();
  1046. // Negate the y and z values of the rotation to convert
  1047. // from Unity to Maya coordinates (left to righthanded).
  1048. return new FbxDouble3 (vector4.X, -vector4.Y, -vector4.Z);
  1049. }
  1050. internal static FbxVector4 ConvertQuaternionToXYZEuler (FbxQuaternion quat)
  1051. {
  1052. FbxAMatrix m = new FbxAMatrix ();
  1053. m.SetQ (quat);
  1054. var vector4 = m.GetR ();
  1055. // Negate the y and z values of the rotation to convert
  1056. // from Unity to Maya coordinates (left to righthanded).
  1057. return new FbxVector4 (vector4.X, -vector4.Y, -vector4.Z, vector4.W);
  1058. }
  1059. internal static FbxDouble3 ToFbxDouble3(Vector3 v)
  1060. {
  1061. return new FbxDouble3(v.x, v.y, v.z);
  1062. }
  1063. internal static FbxDouble3 ToFbxDouble3(FbxVector4 v)
  1064. {
  1065. return new FbxDouble3(v.X, v.Y, v.Z);
  1066. }
  1067. internal static FbxVector4 ToFbxVector4(FbxDouble3 v)
  1068. {
  1069. return new FbxVector4(v.X, v.Y, v.Z);
  1070. }
  1071. internal static FbxDouble3 ConvertToRightHandedEuler(Vector3 rot)
  1072. {
  1073. rot.y *= -1;
  1074. rot.z *= -1;
  1075. return ToFbxDouble3(rot);
  1076. }
  1077. /// <summary>
  1078. /// Euler to quaternion without axis conversion.
  1079. /// </summary>
  1080. /// <returns>a quaternion.</returns>
  1081. /// <param name="euler">Euler.</param>
  1082. internal static FbxQuaternion EulerToQuaternion(FbxVector4 euler)
  1083. {
  1084. FbxAMatrix m = new FbxAMatrix ();
  1085. m.SetR (euler);
  1086. return m.GetQ ();
  1087. }
  1088. /// <summary>
  1089. /// Quaternion to euler without axis conversion.
  1090. /// </summary>
  1091. /// <returns>a euler.</returns>
  1092. /// <param name="quat">Quaternion.</param>
  1093. internal static FbxVector4 QuaternionToEuler(FbxQuaternion quat)
  1094. {
  1095. FbxAMatrix m = new FbxAMatrix ();
  1096. m.SetQ (quat);
  1097. return m.GetR ();
  1098. }
  1099. // get a fbxNode's global default position.
  1100. internal bool ExportTransform (UnityEngine.Transform unityTransform, FbxNode fbxNode, Vector3 newCenter, TransformExportType exportType)
  1101. {
  1102. // Fbx rotation order is XYZ, but Unity rotation order is ZXY.
  1103. // This causes issues when converting euler to quaternion, causing the final
  1104. // rotation to be slighlty off.
  1105. // Fixed by exporting the rotations as eulers with XYZ rotation order.
  1106. // Can't just set the rotation order to ZXY on export as Maya incorrectly imports the
  1107. // rotation. Appears to first convert to XYZ rotation then set rotation order to ZXY.
  1108. fbxNode.SetRotationOrder (FbxNode.EPivotSet.eSourcePivot, FbxEuler.EOrder.eOrderXYZ);
  1109. UnityEngine.Vector3 unityTranslate;
  1110. FbxDouble3 fbxRotate;
  1111. UnityEngine.Vector3 unityScale;
  1112. switch (exportType) {
  1113. case TransformExportType.Reset:
  1114. unityTranslate = Vector3.zero;
  1115. fbxRotate = new FbxDouble3(0);
  1116. unityScale = Vector3.one;
  1117. break;
  1118. case TransformExportType.Global:
  1119. unityTranslate = GetRecenteredTranslation(unityTransform, newCenter);
  1120. fbxRotate = ConvertQuaternionToXYZEuler(unityTransform.rotation);
  1121. unityScale = unityTransform.lossyScale;
  1122. break;
  1123. default: /*case TransformExportType.Local*/
  1124. unityTranslate = unityTransform.localPosition;
  1125. fbxRotate = ConvertQuaternionToXYZEuler(unityTransform.localRotation);
  1126. unityScale = unityTransform.localScale;
  1127. break;
  1128. }
  1129. // Transfer transform data from Unity to Fbx
  1130. var fbxTranslate = ConvertToRightHanded(unityTranslate, UnitScaleFactor);
  1131. var fbxScale = new FbxDouble3 (unityScale.x, unityScale.y, unityScale.z);
  1132. // set the local position of fbxNode
  1133. fbxNode.LclTranslation.Set (new FbxDouble3(fbxTranslate.X, fbxTranslate.Y, fbxTranslate.Z));
  1134. fbxNode.LclRotation.Set (fbxRotate);
  1135. fbxNode.LclScaling.Set (fbxScale);
  1136. return true;
  1137. }
  1138. /// <summary>
  1139. /// if this game object is a model prefab or the model has already been exported, then export with shared components
  1140. /// </summary>
  1141. [SecurityPermission(SecurityAction.LinkDemand)]
  1142. private bool ExportInstance(GameObject unityGo, FbxScene fbxScene, FbxNode fbxNode)
  1143. {
  1144. if (!unityGo || fbxNode == null)
  1145. {
  1146. return false;
  1147. }
  1148. Object unityPrefabParent = PrefabUtility.GetCorrespondingObjectFromSource(unityGo);
  1149. FbxMesh fbxMesh = null;
  1150. if (unityPrefabParent != null && !SharedMeshes.TryGetValue (unityPrefabParent.GetInstanceID(), out fbxMesh))
  1151. {
  1152. if (Verbose)
  1153. Debug.Log (string.Format ("exporting instance {0}({1})", unityGo.name, unityPrefabParent.name));
  1154. if (ExportMesh (unityGo, fbxNode) && fbxNode.GetMesh() != null) {
  1155. SharedMeshes [unityPrefabParent.GetInstanceID()] = fbxNode.GetMesh ();
  1156. return true;
  1157. }
  1158. return false;
  1159. }
  1160. // check if mesh is shared between 2 objects that are not prefabs
  1161. else if (unityPrefabParent == null)
  1162. {
  1163. // check if same mesh has already been exported
  1164. MeshFilter unityGoMesh = unityGo.GetComponent<MeshFilter>();
  1165. if (unityGoMesh != null && MeshToFbxNodeMap.ContainsKey(unityGoMesh.sharedMesh))
  1166. {
  1167. fbxMesh = MeshToFbxNodeMap[unityGoMesh.sharedMesh].GetMesh();
  1168. }
  1169. // export mesh as normal and add it to list
  1170. else
  1171. {
  1172. if (unityGoMesh != null)
  1173. {
  1174. MeshToFbxNodeMap.Add(unityGoMesh.sharedMesh, fbxNode);
  1175. }
  1176. return false;
  1177. }
  1178. }
  1179. if (fbxMesh == null)
  1180. {
  1181. return false;
  1182. }
  1183. // We don't export the mesh because we already have it from the parent, but we still need to assign the material
  1184. var renderer = unityGo.GetComponent<Renderer>();
  1185. var materials = renderer ? renderer.sharedMaterials : null;
  1186. Autodesk.Fbx.FbxSurfaceMaterial newMaterial = null;
  1187. if (materials != null)
  1188. {
  1189. foreach (var mat in materials) {
  1190. if (MaterialMap.TryGetValue(mat.GetInstanceID(), out newMaterial))
  1191. {
  1192. fbxNode.AddMaterial(newMaterial);
  1193. }
  1194. else
  1195. {
  1196. // create new material
  1197. ExportMaterial(mat, fbxScene, fbxNode);
  1198. }
  1199. }
  1200. }
  1201. // set the fbxNode containing the mesh
  1202. fbxNode.SetNodeAttribute (fbxMesh);
  1203. fbxNode.SetShadingMode (FbxNode.EShadingMode.eWireFrame);
  1204. return true;
  1205. }
  1206. /// <summary>
  1207. /// Exports camera component
  1208. /// </summary>
  1209. private bool ExportCamera (GameObject unityGO, FbxScene fbxScene, FbxNode fbxNode)
  1210. {
  1211. if (!unityGO || fbxScene == null || fbxNode == null)
  1212. {
  1213. return false;
  1214. }
  1215. Camera unityCamera = unityGO.GetComponent<Camera> ();
  1216. if (unityCamera == null) {
  1217. return false;
  1218. }
  1219. FbxCamera fbxCamera = FbxCamera.Create (fbxScene.GetFbxManager(), unityCamera.name);
  1220. if (fbxCamera == null) {
  1221. return false;
  1222. }
  1223. CameraVisitor.ConfigureCamera(unityCamera, fbxCamera);
  1224. fbxNode.SetNodeAttribute (fbxCamera);
  1225. // set +90 post rotation to counteract for FBX camera's facing +X direction by default
  1226. fbxNode.SetPostRotation(FbxNode.EPivotSet.eSourcePivot, new FbxVector4(0,90,0));
  1227. // have to set rotation active to true in order for post rotation to be applied
  1228. fbxNode.SetRotationActive (true);
  1229. // make the last camera exported the default camera
  1230. DefaultCamera = fbxNode.GetName ();
  1231. return true;
  1232. }
  1233. /// <summary>
  1234. /// Exports light component.
  1235. /// Supported types: point, spot and directional
  1236. /// Cookie => Gobo
  1237. /// </summary>
  1238. private bool ExportLight (GameObject unityGo, FbxScene fbxScene, FbxNode fbxNode)
  1239. {
  1240. if(!unityGo || fbxScene == null || fbxNode == null)
  1241. {
  1242. return false;
  1243. }
  1244. Light unityLight = unityGo.GetComponent<Light> ();
  1245. if (unityLight == null)
  1246. return false;
  1247. FbxLight.EType fbxLightType;
  1248. // Is light type supported?
  1249. if (!MapLightType.TryGetValue (unityLight.type, out fbxLightType))
  1250. return false;
  1251. FbxLight fbxLight = FbxLight.Create (fbxScene.GetFbxManager (), unityLight.name);
  1252. // Set the type of the light.
  1253. fbxLight.LightType.Set(fbxLightType);
  1254. switch (unityLight.type)
  1255. {
  1256. case LightType.Directional : {
  1257. break;
  1258. }
  1259. case LightType.Spot : {
  1260. // Set the angle of the light's spotlight cone in degrees.
  1261. fbxLight.InnerAngle.Set(unityLight.spotAngle);
  1262. fbxLight.OuterAngle.Set(unityLight.spotAngle);
  1263. break;
  1264. }
  1265. case LightType.Point : {
  1266. break;
  1267. }
  1268. case LightType.Area : {
  1269. // TODO: areaSize: The size of the area light by scaling the node XY
  1270. break;
  1271. }
  1272. }
  1273. // The color of the light.
  1274. var unityLightColor = unityLight.color;
  1275. fbxLight.Color.Set (new FbxDouble3(unityLightColor.r, unityLightColor.g, unityLightColor.b));
  1276. // Set the Intensity of a light is multiplied with the Light color.
  1277. fbxLight.Intensity.Set (unityLight.intensity * UnitScaleFactor /*compensate for Maya scaling by system units*/ );
  1278. // Set the range of the light.
  1279. // applies-to: Point & Spot
  1280. // => FarAttenuationStart, FarAttenuationEnd
  1281. fbxLight.FarAttenuationStart.Set (0.01f /* none zero start */);
  1282. fbxLight.FarAttenuationEnd.Set(unityLight.range*UnitScaleFactor);
  1283. // shadows Set how this light casts shadows
  1284. // applies-to: Point & Spot
  1285. bool unityLightCastShadows = unityLight.shadows != LightShadows.None;
  1286. fbxLight.CastShadows.Set (unityLightCastShadows);
  1287. fbxNode.SetNodeAttribute (fbxLight);
  1288. // set +90 post rotation on x to counteract for FBX light's facing -Y direction by default
  1289. fbxNode.SetPostRotation(FbxNode.EPivotSet.eSourcePivot, new FbxVector4(90,0,0));
  1290. // have to set rotation active to true in order for post rotation to be applied
  1291. fbxNode.SetRotationActive (true);
  1292. return true;
  1293. }
  1294. private bool ExportCommonConstraintProperties<TUnityConstraint,TFbxConstraint>(TUnityConstraint uniConstraint, TFbxConstraint fbxConstraint, FbxNode fbxNode)
  1295. where TUnityConstraint : IConstraint where TFbxConstraint : FbxConstraint
  1296. {
  1297. fbxConstraint.Active.Set(uniConstraint.constraintActive);
  1298. fbxConstraint.Lock.Set(uniConstraint.locked);
  1299. fbxConstraint.Weight.Set(uniConstraint.weight * UnitScaleFactor);
  1300. AddFbxNodeToConstraintsMapping(fbxNode, fbxConstraint, typeof(TUnityConstraint));
  1301. return true;
  1302. }
  1303. private struct ExpConstraintSource
  1304. {
  1305. private FbxNode m_node;
  1306. public FbxNode node
  1307. {
  1308. get { return m_node; }
  1309. set { m_node = value; }
  1310. }
  1311. private float m_weight;
  1312. public float weight
  1313. {
  1314. get { return m_weight; }
  1315. set { m_weight = value; }
  1316. }
  1317. public ExpConstraintSource(FbxNode node, float weight)
  1318. {
  1319. this.m_node = node;
  1320. this.m_weight = weight;
  1321. }
  1322. }
  1323. private List<ExpConstraintSource> GetConstraintSources(IConstraint unityConstraint)
  1324. {
  1325. if(unityConstraint == null)
  1326. {
  1327. return null;
  1328. }
  1329. var fbxSources = new List<ExpConstraintSource>();
  1330. var sources = new List<ConstraintSource>();
  1331. unityConstraint.GetSources(sources);
  1332. foreach (var source in sources)
  1333. {
  1334. // ignore any sources that are not getting exported
  1335. FbxNode sourceNode;
  1336. if (!MapUnityObjectToFbxNode.TryGetValue(source.sourceTransform.gameObject, out sourceNode))
  1337. {
  1338. continue;
  1339. }
  1340. fbxSources.Add(new ExpConstraintSource(sourceNode, source.weight * UnitScaleFactor));
  1341. }
  1342. return fbxSources;
  1343. }
  1344. private void AddFbxNodeToConstraintsMapping<T>(FbxNode fbxNode, T fbxConstraint, System.Type uniConstraintType) where T : FbxConstraint
  1345. {
  1346. Dictionary<FbxConstraint, System.Type> constraintMapping;
  1347. if (!MapConstrainedObjectToConstraints.TryGetValue(fbxNode, out constraintMapping))
  1348. {
  1349. constraintMapping = new Dictionary<FbxConstraint, System.Type>();
  1350. MapConstrainedObjectToConstraints.Add(fbxNode, constraintMapping);
  1351. }
  1352. constraintMapping.Add(fbxConstraint, uniConstraintType);
  1353. }
  1354. private bool ExportPositionConstraint(IConstraint uniConstraint, FbxScene fbxScene, FbxNode fbxNode)
  1355. {
  1356. if(fbxNode == null)
  1357. {
  1358. return false;
  1359. }
  1360. var uniPosConstraint = uniConstraint as PositionConstraint;
  1361. Debug.Assert (uniPosConstraint != null);
  1362. FbxConstraintPosition fbxPosConstraint = FbxConstraintPosition.Create(fbxScene, fbxNode.GetName() + "_positionConstraint");
  1363. fbxPosConstraint.SetConstrainedObject(fbxNode);
  1364. var uniSources = GetConstraintSources(uniPosConstraint);
  1365. uniSources.ForEach(uniSource => fbxPosConstraint.AddConstraintSource(uniSource.node, uniSource.weight));
  1366. ExportCommonConstraintProperties(uniPosConstraint, fbxPosConstraint, fbxNode);
  1367. var uniAffectedAxes = uniPosConstraint.translationAxis;
  1368. fbxPosConstraint.AffectX.Set((uniAffectedAxes & Axis.X) == Axis.X);
  1369. fbxPosConstraint.AffectY.Set((uniAffectedAxes & Axis.Y) == Axis.Y);
  1370. fbxPosConstraint.AffectZ.Set((uniAffectedAxes & Axis.Z) == Axis.Z);
  1371. var fbxTranslationOffset = ConvertToRightHanded(uniPosConstraint.translationOffset, UnitScaleFactor);
  1372. fbxPosConstraint.Translation.Set(ToFbxDouble3(fbxTranslationOffset));
  1373. // rest position is the position of the fbx node
  1374. var fbxRestTranslation = ConvertToRightHanded(uniPosConstraint.translationAtRest, UnitScaleFactor);
  1375. // set the local position of fbxNode
  1376. fbxNode.LclTranslation.Set(ToFbxDouble3(fbxRestTranslation));
  1377. return true;
  1378. }
  1379. private bool ExportRotationConstraint(IConstraint uniConstraint, FbxScene fbxScene, FbxNode fbxNode)
  1380. {
  1381. if(fbxNode == null)
  1382. {
  1383. return false;
  1384. }
  1385. var uniRotConstraint = uniConstraint as RotationConstraint;
  1386. Debug.Assert(uniRotConstraint != null);
  1387. FbxConstraintRotation fbxRotConstraint = FbxConstraintRotation.Create(fbxScene, fbxNode.GetName() + "_rotationConstraint");
  1388. fbxRotConstraint.SetConstrainedObject(fbxNode);
  1389. var uniSources = GetConstraintSources(uniRotConstraint);
  1390. uniSources.ForEach(uniSource => fbxRotConstraint.AddConstraintSource(uniSource.node, uniSource.weight));
  1391. ExportCommonConstraintProperties(uniRotConstraint, fbxRotConstraint, fbxNode);
  1392. var uniAffectedAxes = uniRotConstraint.rotationAxis;
  1393. fbxRotConstraint.AffectX.Set((uniAffectedAxes & Axis.X) == Axis.X);
  1394. fbxRotConstraint.AffectY.Set((uniAffectedAxes & Axis.Y) == Axis.Y);
  1395. fbxRotConstraint.AffectZ.Set((uniAffectedAxes & Axis.Z) == Axis.Z);
  1396. // Not converting rotation offset to XYZ euler as it gives the incorrect result in both Maya and Unity.
  1397. var uniRotationOffset = uniRotConstraint.rotationOffset;
  1398. var fbxRotationOffset = ConvertToRightHandedEuler(uniRotationOffset);
  1399. fbxRotConstraint.Rotation.Set(fbxRotationOffset);
  1400. // rest rotation is the rotation of the fbx node
  1401. var uniRestRotationQuat = Quaternion.Euler(uniRotConstraint.rotationAtRest);
  1402. var fbxRestRotation = ConvertQuaternionToXYZEuler(uniRestRotationQuat);
  1403. // set the local rotation of fbxNode
  1404. fbxNode.LclRotation.Set(fbxRestRotation);
  1405. return true;
  1406. }
  1407. private bool ExportScaleConstraint(IConstraint uniConstraint, FbxScene fbxScene, FbxNode fbxNode)
  1408. {
  1409. if(fbxNode == null)
  1410. {
  1411. return false;
  1412. }
  1413. var uniScaleConstraint = uniConstraint as ScaleConstraint;
  1414. Debug.Assert(uniScaleConstraint != null);
  1415. FbxConstraintScale fbxScaleConstraint = FbxConstraintScale.Create(fbxScene, fbxNode.GetName() + "_scaleConstraint");
  1416. fbxScaleConstraint.SetConstrainedObject(fbxNode);
  1417. var uniSources = GetConstraintSources(uniScaleConstraint);
  1418. uniSources.ForEach(uniSource => fbxScaleConstraint.AddConstraintSource(uniSource.node, uniSource.weight));
  1419. ExportCommonConstraintProperties(uniScaleConstraint, fbxScaleConstraint, fbxNode);
  1420. var uniAffectedAxes = uniScaleConstraint.scalingAxis;
  1421. fbxScaleConstraint.AffectX.Set((uniAffectedAxes & Axis.X) == Axis.X);
  1422. fbxScaleConstraint.AffectY.Set((uniAffectedAxes & Axis.Y) == Axis.Y);
  1423. fbxScaleConstraint.AffectZ.Set((uniAffectedAxes & Axis.Z) == Axis.Z);
  1424. var uniScaleOffset = uniScaleConstraint.scaleOffset;
  1425. var fbxScalingOffset = ToFbxDouble3(uniScaleOffset);
  1426. fbxScaleConstraint.Scaling.Set(fbxScalingOffset);
  1427. // rest rotation is the rotation of the fbx node
  1428. var uniRestScale = uniScaleConstraint.scaleAtRest;
  1429. var fbxRestScale = ToFbxDouble3(uniRestScale);
  1430. // set the local rotation of fbxNode
  1431. fbxNode.LclScaling.Set(fbxRestScale);
  1432. return true;
  1433. }
  1434. private bool ExportAimConstraint(IConstraint uniConstraint, FbxScene fbxScene, FbxNode fbxNode)
  1435. {
  1436. if(fbxNode == null)
  1437. {
  1438. return false;
  1439. }
  1440. var uniAimConstraint = uniConstraint as AimConstraint;
  1441. Debug.Assert(uniAimConstraint != null);
  1442. FbxConstraintAim fbxAimConstraint = FbxConstraintAim.Create(fbxScene, fbxNode.GetName() + "_aimConstraint");
  1443. fbxAimConstraint.SetConstrainedObject(fbxNode);
  1444. var uniSources = GetConstraintSources(uniAimConstraint);
  1445. uniSources.ForEach(uniSource => fbxAimConstraint.AddConstraintSource(uniSource.node, uniSource.weight));
  1446. ExportCommonConstraintProperties(uniAimConstraint, fbxAimConstraint, fbxNode);
  1447. var uniAffectedAxes = uniAimConstraint.rotationAxis;
  1448. fbxAimConstraint.AffectX.Set((uniAffectedAxes & Axis.X) == Axis.X);
  1449. fbxAimConstraint.AffectY.Set((uniAffectedAxes & Axis.Y) == Axis.Y);
  1450. fbxAimConstraint.AffectZ.Set((uniAffectedAxes & Axis.Z) == Axis.Z);
  1451. var uniRotationOffset = uniAimConstraint.rotationOffset;
  1452. var fbxRotationOffset = ConvertToRightHandedEuler(uniRotationOffset);
  1453. fbxAimConstraint.RotationOffset.Set(fbxRotationOffset);
  1454. // rest rotation is the rotation of the fbx node
  1455. var uniRestRotationQuat = Quaternion.Euler(uniAimConstraint.rotationAtRest);
  1456. var fbxRestRotation = ConvertQuaternionToXYZEuler(uniRestRotationQuat);
  1457. // set the local rotation of fbxNode
  1458. fbxNode.LclRotation.Set(fbxRestRotation);
  1459. FbxConstraintAim.EWorldUp fbxWorldUpType = FbxConstraintAim.EWorldUp.eAimAtNone;
  1460. switch (uniAimConstraint.worldUpType)
  1461. {
  1462. case AimConstraint.WorldUpType.None:
  1463. fbxWorldUpType = FbxConstraintAim.EWorldUp.eAimAtNone;
  1464. break;
  1465. case AimConstraint.WorldUpType.ObjectRotationUp:
  1466. fbxWorldUpType = FbxConstraintAim.EWorldUp.eAimAtObjectRotationUp;
  1467. break;
  1468. case AimConstraint.WorldUpType.ObjectUp:
  1469. fbxWorldUpType = FbxConstraintAim.EWorldUp.eAimAtObjectUp;
  1470. break;
  1471. case AimConstraint.WorldUpType.SceneUp:
  1472. fbxWorldUpType = FbxConstraintAim.EWorldUp.eAimAtSceneUp;
  1473. break;
  1474. case AimConstraint.WorldUpType.Vector:
  1475. fbxWorldUpType = FbxConstraintAim.EWorldUp.eAimAtVector;
  1476. break;
  1477. default:
  1478. throw new System.NotImplementedException();
  1479. }
  1480. fbxAimConstraint.WorldUpType.Set((int)fbxWorldUpType);
  1481. var uniAimVector = ConvertToRightHanded(uniAimConstraint.aimVector);
  1482. fbxAimConstraint.AimVector.Set(ToFbxDouble3(uniAimVector));
  1483. fbxAimConstraint.UpVector.Set(ToFbxDouble3(uniAimConstraint.upVector));
  1484. fbxAimConstraint.WorldUpVector.Set(ToFbxDouble3(uniAimConstraint.worldUpVector));
  1485. if (uniAimConstraint.worldUpObject && MapUnityObjectToFbxNode.ContainsKey(uniAimConstraint.worldUpObject.gameObject))
  1486. {
  1487. fbxAimConstraint.SetWorldUpObject(MapUnityObjectToFbxNode[uniAimConstraint.worldUpObject.gameObject]);
  1488. }
  1489. return true;
  1490. }
  1491. private bool ExportParentConstraint(IConstraint uniConstraint, FbxScene fbxScene, FbxNode fbxNode)
  1492. {
  1493. if(fbxNode == null)
  1494. {
  1495. return false;
  1496. }
  1497. var uniParentConstraint = uniConstraint as ParentConstraint;
  1498. Debug.Assert(uniParentConstraint != null);
  1499. FbxConstraintParent fbxParentConstraint = FbxConstraintParent.Create(fbxScene, fbxNode.GetName() + "_parentConstraint");
  1500. fbxParentConstraint.SetConstrainedObject(fbxNode);
  1501. var uniSources = GetConstraintSources(uniParentConstraint);
  1502. var uniTranslationOffsets = uniParentConstraint.translationOffsets;
  1503. var uniRotationOffsets = uniParentConstraint.rotationOffsets;
  1504. for(int i = 0; i < uniSources.Count; i++)
  1505. {
  1506. var uniSource = uniSources[i];
  1507. var uniTranslationOffset = uniTranslationOffsets[i];
  1508. var uniRotationOffset = uniRotationOffsets[i];
  1509. fbxParentConstraint.AddConstraintSource(uniSource.node, uniSource.weight);
  1510. var fbxTranslationOffset = ConvertToRightHanded(uniTranslationOffset, UnitScaleFactor);
  1511. fbxParentConstraint.SetTranslationOffset(uniSource.node, fbxTranslationOffset);
  1512. var fbxRotationOffset = ToFbxVector4(ConvertToRightHandedEuler(uniRotationOffset));
  1513. fbxParentConstraint.SetRotationOffset(uniSource.node, fbxRotationOffset);
  1514. }
  1515. ExportCommonConstraintProperties(uniParentConstraint, fbxParentConstraint, fbxNode);
  1516. var uniTranslationAxes = uniParentConstraint.translationAxis;
  1517. fbxParentConstraint.AffectTranslationX.Set((uniTranslationAxes & Axis.X) == Axis.X);
  1518. fbxParentConstraint.AffectTranslationY.Set((uniTranslationAxes & Axis.Y) == Axis.Y);
  1519. fbxParentConstraint.AffectTranslationZ.Set((uniTranslationAxes & Axis.Z) == Axis.Z);
  1520. var uniRotationAxes = uniParentConstraint.rotationAxis;
  1521. fbxParentConstraint.AffectRotationX.Set((uniRotationAxes & Axis.X) == Axis.X);
  1522. fbxParentConstraint.AffectRotationY.Set((uniRotationAxes & Axis.Y) == Axis.Y);
  1523. fbxParentConstraint.AffectRotationZ.Set((uniRotationAxes & Axis.Z) == Axis.Z);
  1524. // rest position is the position of the fbx node
  1525. var fbxRestTranslation = ConvertToRightHanded(uniParentConstraint.translationAtRest, UnitScaleFactor);
  1526. // set the local position of fbxNode
  1527. fbxNode.LclTranslation.Set(ToFbxDouble3(fbxRestTranslation));
  1528. // rest rotation is the rotation of the fbx node
  1529. var uniRestRotationQuat = Quaternion.Euler(uniParentConstraint.rotationAtRest);
  1530. var fbxRestRotation = ConvertQuaternionToXYZEuler(uniRestRotationQuat);
  1531. // set the local rotation of fbxNode
  1532. fbxNode.LclRotation.Set(fbxRestRotation);
  1533. return true;
  1534. }
  1535. private delegate bool ExportConstraintDelegate(IConstraint c , FbxScene fs, FbxNode fn);
  1536. private bool ExportConstraints (GameObject unityGo, FbxScene fbxScene, FbxNode fbxNode)
  1537. {
  1538. if (!unityGo)
  1539. {
  1540. return false;
  1541. }
  1542. var mapConstraintTypeToExportFunction = new Dictionary<System.Type, ExportConstraintDelegate>()
  1543. {
  1544. { typeof(PositionConstraint), ExportPositionConstraint },
  1545. { typeof(RotationConstraint), ExportRotationConstraint },
  1546. { typeof(ScaleConstraint), ExportScaleConstraint },
  1547. { typeof(AimConstraint), ExportAimConstraint },
  1548. { typeof(ParentConstraint), ExportParentConstraint }
  1549. };
  1550. // check if GameObject has one of the 5 supported constraints: aim, parent, position, rotation, scale
  1551. var uniConstraints = unityGo.GetComponents<IConstraint>();
  1552. foreach(var uniConstraint in uniConstraints)
  1553. {
  1554. var uniConstraintType = uniConstraint.GetType();
  1555. ExportConstraintDelegate constraintDelegate;
  1556. if (!mapConstraintTypeToExportFunction.TryGetValue(uniConstraintType, out constraintDelegate))
  1557. {
  1558. Debug.LogWarningFormat("FbxExporter: Missing function to export constraint of type {0}", uniConstraintType.Name);
  1559. continue;
  1560. }
  1561. constraintDelegate(uniConstraint, fbxScene, fbxNode);
  1562. }
  1563. return true;
  1564. }
  1565. /// <summary>
  1566. /// Return set of sample times to cover all keys on animation curves
  1567. /// </summary>
  1568. [SecurityPermission(SecurityAction.LinkDemand)]
  1569. internal static HashSet<float> GetSampleTimes(AnimationCurve[] animCurves, double sampleRate)
  1570. {
  1571. var keyTimes = new HashSet<float>();
  1572. double fs = 1.0/sampleRate;
  1573. double firstTime = double.MaxValue, lastTime = double.MinValue;
  1574. foreach (var ac in animCurves)
  1575. {
  1576. if (ac==null || ac.length<=0) continue;
  1577. firstTime = System.Math.Min(firstTime, ac[0].time);
  1578. lastTime = System.Math.Max(lastTime, ac[ac.length-1].time);
  1579. }
  1580. int firstframe = (int)System.Math.Floor(firstTime * sampleRate);
  1581. int lastframe = (int)System.Math.Ceiling(lastTime * sampleRate);
  1582. for (int i = firstframe; i <= lastframe; i++) {
  1583. keyTimes.Add ((float)(i * fs));
  1584. }
  1585. return keyTimes;
  1586. }
  1587. /// <summary>
  1588. /// Return set of all keys times on animation curves
  1589. /// </summary>
  1590. [SecurityPermission(SecurityAction.LinkDemand)]
  1591. internal static HashSet<float> GetKeyTimes(AnimationCurve[] animCurves)
  1592. {
  1593. var keyTimes = new HashSet<float>();
  1594. foreach (var ac in animCurves)
  1595. {
  1596. if (ac!=null) foreach(var key in ac.keys) { keyTimes.Add(key.time); }
  1597. }
  1598. return keyTimes;
  1599. }
  1600. /// <summary>
  1601. /// Export animation curve key frames with key tangents
  1602. /// NOTE : This is a work in progress (WIP). We only export the key time and value on
  1603. /// a Cubic curve using the default tangents.
  1604. /// </summary>
  1605. internal void ExportAnimationKeys (AnimationCurve uniAnimCurve, FbxAnimCurve fbxAnimCurve,
  1606. UnityToMayaConvertSceneHelper convertSceneHelper)
  1607. {
  1608. // TODO: complete the mapping between key tangents modes Unity and FBX
  1609. Dictionary<AnimationUtility.TangentMode, List<FbxAnimCurveDef.ETangentMode>> MapUnityKeyTangentModeToFBX =
  1610. new Dictionary<AnimationUtility.TangentMode, List<FbxAnimCurveDef.ETangentMode>>
  1611. {
  1612. //TangeantAuto|GenericTimeIndependent|GenericClampProgressive
  1613. {AnimationUtility.TangentMode.Free, new List<FbxAnimCurveDef.ETangentMode>{FbxAnimCurveDef.ETangentMode.eTangentAuto,FbxAnimCurveDef.ETangentMode.eTangentGenericClampProgressive}},
  1614. //TangeantAuto|GenericTimeIndependent
  1615. {AnimationUtility.TangentMode.Auto, new List<FbxAnimCurveDef.ETangentMode>{FbxAnimCurveDef.ETangentMode.eTangentAuto,FbxAnimCurveDef.ETangentMode.eTangentGenericTimeIndependent}},
  1616. //TangeantAuto|GenericTimeIndependent|GenericClampProgressive
  1617. {AnimationUtility.TangentMode.ClampedAuto, new List<FbxAnimCurveDef.ETangentMode>{FbxAnimCurveDef.ETangentMode.eTangentAuto,FbxAnimCurveDef.ETangentMode.eTangentGenericClampProgressive}},
  1618. };
  1619. // Copy Unity AnimCurve to FBX AnimCurve.
  1620. // NOTE: only cubic keys are supported by the FbxImporter
  1621. using (new FbxAnimCurveModifyHelper(new List<FbxAnimCurve>{fbxAnimCurve}))
  1622. {
  1623. for (int keyIndex = 0; keyIndex < uniAnimCurve.length; ++keyIndex)
  1624. {
  1625. var uniKeyFrame = uniAnimCurve [keyIndex];
  1626. var fbxTime = FbxTime.FromSecondDouble (uniKeyFrame.time);
  1627. int fbxKeyIndex = fbxAnimCurve.KeyAdd (fbxTime);
  1628. fbxAnimCurve.KeySet (fbxKeyIndex,
  1629. fbxTime,
  1630. convertSceneHelper.Convert(uniKeyFrame.value)
  1631. );
  1632. // configure tangents
  1633. var lTangent = AnimationUtility.GetKeyLeftTangentMode(uniAnimCurve, keyIndex);
  1634. var rTangent = AnimationUtility.GetKeyRightTangentMode(uniAnimCurve, keyIndex);
  1635. if (!(MapUnityKeyTangentModeToFBX.ContainsKey(lTangent) && MapUnityKeyTangentModeToFBX.ContainsKey(rTangent)))
  1636. {
  1637. Debug.LogWarning(string.Format("key[{0}] missing tangent mapping ({1},{2})", keyIndex, lTangent.ToString(), rTangent.ToString()));
  1638. continue;
  1639. }
  1640. // TODO : handle broken tangents
  1641. // TODO : set key tangents
  1642. }
  1643. }
  1644. }
  1645. /// <summary>
  1646. /// Export animation curve key samples
  1647. /// </summary>
  1648. [SecurityPermission(SecurityAction.LinkDemand)]
  1649. internal void ExportAnimationSamples (AnimationCurve uniAnimCurve, FbxAnimCurve fbxAnimCurve,
  1650. double sampleRate,
  1651. UnityToMayaConvertSceneHelper convertSceneHelper)
  1652. {
  1653. using (new FbxAnimCurveModifyHelper(new List<FbxAnimCurve>{fbxAnimCurve}))
  1654. {
  1655. foreach (var currSampleTime in GetSampleTimes(new AnimationCurve[]{uniAnimCurve}, sampleRate))
  1656. {
  1657. float currSampleValue = uniAnimCurve.Evaluate((float)currSampleTime);
  1658. var fbxTime = FbxTime.FromSecondDouble (currSampleTime);
  1659. int fbxKeyIndex = fbxAnimCurve.KeyAdd (fbxTime);
  1660. fbxAnimCurve.KeySet (fbxKeyIndex,
  1661. fbxTime,
  1662. convertSceneHelper.Convert(currSampleValue)
  1663. );
  1664. }
  1665. }
  1666. }
  1667. /// <summary>
  1668. /// Get the FbxConstraint associated with the constrained node.
  1669. /// </summary>
  1670. /// <param name="constrainedNode"></param>
  1671. /// <param name="uniConstraintType"></param>
  1672. /// <returns></returns>
  1673. private FbxConstraint GetFbxConstraint(FbxNode constrainedNode, System.Type uniConstraintType)
  1674. {
  1675. if (uniConstraintType == null || !uniConstraintType.GetInterfaces().Contains(typeof(IConstraint)))
  1676. {
  1677. // not actually a constraint
  1678. return null;
  1679. }
  1680. Dictionary<FbxConstraint, System.Type> constraints;
  1681. if (!MapConstrainedObjectToConstraints.TryGetValue(constrainedNode, out constraints))
  1682. {
  1683. return null;
  1684. }
  1685. foreach (var constraint in constraints)
  1686. {
  1687. if (uniConstraintType != constraint.Value)
  1688. {
  1689. continue;
  1690. }
  1691. return constraint.Key;
  1692. }
  1693. return null;
  1694. }
  1695. private FbxProperty GetFbxProperty(FbxNode fbxNode, string fbxPropertyName, System.Type uniPropertyType)
  1696. {
  1697. if(fbxNode == null)
  1698. {
  1699. return null;
  1700. }
  1701. // check if property maps to a constraint
  1702. // check this first because both constraints and FbxNodes can contain a RotationOffset property,
  1703. // but only the constraint one is animatable.
  1704. var fbxConstraint = GetFbxConstraint(fbxNode, uniPropertyType);
  1705. if(fbxConstraint != null)
  1706. {
  1707. var prop = fbxConstraint.FindProperty(fbxPropertyName, false);
  1708. if (prop.IsValid())
  1709. {
  1710. return prop;
  1711. }
  1712. }
  1713. // map unity property name to fbx property
  1714. var fbxProperty = fbxNode.FindProperty(fbxPropertyName, false);
  1715. if (fbxProperty.IsValid())
  1716. {
  1717. return fbxProperty;
  1718. }
  1719. var fbxNodeAttribute = fbxNode.GetNodeAttribute();
  1720. if (fbxNodeAttribute != null)
  1721. {
  1722. fbxProperty = fbxNodeAttribute.FindProperty(fbxPropertyName, false);
  1723. }
  1724. return fbxProperty;
  1725. }
  1726. /// <summary>
  1727. /// Export an AnimationCurve.
  1728. /// NOTE: This is not used for rotations, because we need to convert from
  1729. /// quaternion to euler and various other stuff.
  1730. /// </summary>
  1731. [SecurityPermission(SecurityAction.LinkDemand)]
  1732. private void ExportAnimationCurve (FbxNode fbxNode,
  1733. AnimationCurve uniAnimCurve,
  1734. float frameRate,
  1735. string uniPropertyName,
  1736. System.Type uniPropertyType,
  1737. FbxAnimLayer fbxAnimLayer)
  1738. {
  1739. if(fbxNode == null)
  1740. {
  1741. return;
  1742. }
  1743. if (Verbose) {
  1744. Debug.Log ("Exporting animation for " + fbxNode.GetName() + " (" + uniPropertyName + ")");
  1745. }
  1746. var fbxConstraint = GetFbxConstraint(fbxNode, uniPropertyType);
  1747. FbxPropertyChannelPair[] fbxPropertyChannelPairs;
  1748. if (!FbxPropertyChannelPair.TryGetValue (uniPropertyName, out fbxPropertyChannelPairs, fbxConstraint)) {
  1749. Debug.LogWarning(string.Format("no mapping from Unity '{0}' to fbx property", uniPropertyName));
  1750. return;
  1751. }
  1752. foreach (var fbxPropertyChannelPair in fbxPropertyChannelPairs) {
  1753. // map unity property name to fbx property
  1754. var fbxProperty = GetFbxProperty(fbxNode, fbxPropertyChannelPair.Property, uniPropertyType);
  1755. if (!fbxProperty.IsValid ()) {
  1756. Debug.LogError (string.Format ("no fbx property {0} found on {1} node or nodeAttribute ", fbxPropertyChannelPair.Property, fbxNode.GetName ()));
  1757. return;
  1758. }
  1759. if (!fbxProperty.GetFlag(FbxPropertyFlags.EFlags.eAnimatable))
  1760. {
  1761. Debug.LogErrorFormat("fbx property {0} found on node {1} is not animatable", fbxPropertyChannelPair.Property, fbxNode.GetName());
  1762. }
  1763. // Create the AnimCurve on the channel
  1764. FbxAnimCurve fbxAnimCurve = fbxProperty.GetCurve (fbxAnimLayer, fbxPropertyChannelPair.Channel, true);
  1765. if(fbxAnimCurve == null)
  1766. {
  1767. return;
  1768. }
  1769. // create a convert scene helper so that we can convert from Unity to Maya
  1770. // AxisSystem (LeftHanded to RightHanded) and FBX's default units
  1771. // (Meters to Centimetres)
  1772. var convertSceneHelper = new UnityToMayaConvertSceneHelper (uniPropertyName, fbxNode);
  1773. // TODO: we'll resample the curve so we don't have to
  1774. // configure tangents
  1775. if (ModelExporter.ExportSettings.BakeAnimationProperty) {
  1776. ExportAnimationSamples (uniAnimCurve, fbxAnimCurve, frameRate, convertSceneHelper);
  1777. } else {
  1778. ExportAnimationKeys (uniAnimCurve, fbxAnimCurve, convertSceneHelper);
  1779. }
  1780. }
  1781. }
  1782. internal class UnityToMayaConvertSceneHelper
  1783. {
  1784. bool convertDistance = false;
  1785. bool convertLtoR = false;
  1786. bool convertToRadian = false;
  1787. bool convertLensShiftX = false;
  1788. bool convertLensShiftY = false;
  1789. FbxCamera camera = null;
  1790. float unitScaleFactor = 1f;
  1791. public UnityToMayaConvertSceneHelper(string uniPropertyName, FbxNode fbxNode)
  1792. {
  1793. System.StringComparison cc = System.StringComparison.CurrentCulture;
  1794. bool partT = uniPropertyName.StartsWith("m_LocalPosition.", cc) || uniPropertyName.StartsWith("m_TranslationOffset", cc);
  1795. bool partTx = uniPropertyName.EndsWith("Position.x", cc) || uniPropertyName.EndsWith("T.x", cc) || (uniPropertyName.StartsWith("m_TranslationOffset") && uniPropertyName.EndsWith(".x", cc));
  1796. bool partRyz = uniPropertyName.StartsWith("m_RotationOffset", cc) && (uniPropertyName.EndsWith(".y") || uniPropertyName.EndsWith(".z"));
  1797. convertLtoR |= partTx;
  1798. convertLtoR |= partRyz;
  1799. convertDistance |= partT;
  1800. convertDistance |= uniPropertyName.StartsWith ("m_Intensity", cc);
  1801. convertDistance |= uniPropertyName.ToLower().EndsWith("weight", cc);
  1802. convertLensShiftX |= uniPropertyName.StartsWith("m_LensShift.x", cc);
  1803. convertLensShiftY |= uniPropertyName.StartsWith("m_LensShift.y", cc);
  1804. if (convertLensShiftX || convertLensShiftY)
  1805. {
  1806. camera = fbxNode.GetCamera();
  1807. }
  1808. // The ParentConstraint's source Rotation Offsets are read in as radians, so make sure they are exported as radians
  1809. convertToRadian = uniPropertyName.StartsWith("m_RotationOffsets.Array.data", cc);
  1810. if (convertDistance)
  1811. unitScaleFactor = ModelExporter.UnitScaleFactor;
  1812. if (convertLtoR)
  1813. unitScaleFactor = -unitScaleFactor;
  1814. if (convertToRadian)
  1815. {
  1816. unitScaleFactor *= (Mathf.PI / 180);
  1817. }
  1818. }
  1819. public float Convert(float value)
  1820. {
  1821. float convertedValue = value;
  1822. if (convertLensShiftX || convertLensShiftY)
  1823. {
  1824. convertedValue = Mathf.Clamp(Mathf.Abs(value), 0f, 1f)*Mathf.Sign(value);
  1825. }
  1826. if (camera != null)
  1827. {
  1828. if (convertLensShiftX)
  1829. {
  1830. convertedValue *= (float)camera.GetApertureWidth();
  1831. }
  1832. else if (convertLensShiftY)
  1833. {
  1834. convertedValue *= (float)camera.GetApertureHeight();
  1835. }
  1836. }
  1837. // left handed to right handed conversion
  1838. // meters to centimetres conversion
  1839. return unitScaleFactor * convertedValue;
  1840. }
  1841. }
  1842. /// <summary>
  1843. /// Export an AnimationClip as a single take
  1844. /// </summary>
  1845. [SecurityPermission(SecurityAction.LinkDemand)]
  1846. private void ExportAnimationClip (AnimationClip uniAnimClip, GameObject uniRoot, FbxScene fbxScene)
  1847. {
  1848. if (!uniAnimClip || !uniRoot || fbxScene == null) return;
  1849. if (Verbose)
  1850. Debug.Log (string.Format ("Exporting animation clip ({1}) for {0}", uniRoot.name, uniAnimClip.name));
  1851. // setup anim stack
  1852. FbxAnimStack fbxAnimStack = FbxAnimStack.Create (fbxScene, uniAnimClip.name);
  1853. fbxAnimStack.Description.Set ("Animation Take: " + uniAnimClip.name);
  1854. // add one mandatory animation layer
  1855. FbxAnimLayer fbxAnimLayer = FbxAnimLayer.Create (fbxScene, "Animation Base Layer");
  1856. fbxAnimStack.AddMember (fbxAnimLayer);
  1857. // Set up the FPS so our frame-relative math later works out
  1858. // Custom frame rate isn't really supported in FBX SDK (there's
  1859. // a bug), so try hard to find the nearest time mode.
  1860. FbxTime.EMode timeMode = FbxTime.EMode.eCustom;
  1861. double precision = 1e-6;
  1862. while (timeMode == FbxTime.EMode.eCustom && precision < 1000) {
  1863. timeMode = FbxTime.ConvertFrameRateToTimeMode (uniAnimClip.frameRate, precision);
  1864. precision *= 10;
  1865. }
  1866. if (timeMode == FbxTime.EMode.eCustom) {
  1867. timeMode = FbxTime.EMode.eFrames30;
  1868. }
  1869. fbxScene.GetGlobalSettings ().SetTimeMode (timeMode);
  1870. // set time correctly
  1871. var fbxStartTime = FbxTime.FromSecondDouble (0);
  1872. var fbxStopTime = FbxTime.FromSecondDouble (uniAnimClip.length);
  1873. fbxAnimStack.SetLocalTimeSpan (new FbxTimeSpan (fbxStartTime, fbxStopTime));
  1874. var unityCurves = new Dictionary<GameObject, List<UnityCurve>> ();
  1875. // extract and store all necessary information from the curve bindings, namely the animation curves
  1876. // and their corresponding property names for each GameObject.
  1877. foreach (EditorCurveBinding uniCurveBinding in AnimationUtility.GetCurveBindings (uniAnimClip)) {
  1878. Object uniObj = AnimationUtility.GetAnimatedObject (uniRoot, uniCurveBinding);
  1879. if (!uniObj) {
  1880. continue;
  1881. }
  1882. AnimationCurve uniAnimCurve = AnimationUtility.GetEditorCurve (uniAnimClip, uniCurveBinding);
  1883. if (uniAnimCurve == null) {
  1884. continue;
  1885. }
  1886. var uniGO = GetGameObject (uniObj);
  1887. // Check if the GameObject has an FBX node to the animation. It might be null because the LOD selected doesn't match the one on the gameobject.
  1888. if (!uniGO || MapUnityObjectToFbxNode.ContainsKey(uniGO) == false) {
  1889. continue;
  1890. }
  1891. if (unityCurves.ContainsKey (uniGO)) {
  1892. unityCurves [uniGO].Add (new UnityCurve(uniCurveBinding.propertyName, uniAnimCurve, uniCurveBinding.type));
  1893. continue;
  1894. }
  1895. unityCurves.Add (uniGO, new List<UnityCurve> (){ new UnityCurve(uniCurveBinding.propertyName, uniAnimCurve, uniCurveBinding.type) });
  1896. }
  1897. // transfer root motion
  1898. var animSource = ExportOptions.AnimationSource;
  1899. var animDest = ExportOptions.AnimationDest;
  1900. if (animSource && animDest && animSource != animDest) {
  1901. // list of all transforms between source and dest, including source and dest
  1902. var transformsFromSourceToDest = new List<Transform> ();
  1903. var curr = animDest;
  1904. while (curr != animSource) {
  1905. transformsFromSourceToDest.Add (curr);
  1906. curr = curr.parent;
  1907. }
  1908. transformsFromSourceToDest.Add (animSource);
  1909. transformsFromSourceToDest.Reverse ();
  1910. // while there are 2 transforms in the list, transfer the animation from the
  1911. // first to the next transform.
  1912. // Then remove the first transform from the list.
  1913. while (transformsFromSourceToDest.Count >= 2) {
  1914. var source = transformsFromSourceToDest [0];
  1915. transformsFromSourceToDest.RemoveAt (0);
  1916. var dest = transformsFromSourceToDest [0];
  1917. TransferMotion (source, dest, uniAnimClip.frameRate, ref unityCurves);
  1918. }
  1919. }
  1920. /* The major difficulty: Unity uses quaternions for rotation
  1921. * (which is how it should be) but FBX uses Euler angles. So we
  1922. * need to gather up the list of transform curves per object.
  1923. *
  1924. * For euler angles, Unity uses ZXY rotation order while Maya uses XYZ.
  1925. * Maya doesn't import files with ZXY rotation correctly, so have to convert to XYZ.
  1926. * Need all 3 curves in order to convert.
  1927. *
  1928. * Also, in both cases, prerotation has to be removed from the animated rotation if
  1929. * there are bones being exported.
  1930. */
  1931. var rotations = new Dictionary<GameObject, RotationCurve>();
  1932. // export the animation curves for each GameObject that has animation
  1933. foreach (var kvp in unityCurves) {
  1934. var uniGO = kvp.Key;
  1935. foreach (var uniCurve in kvp.Value) {
  1936. var propertyName = uniCurve.propertyName;
  1937. var uniAnimCurve = uniCurve.uniAnimCurve;
  1938. // Do not create the curves if the component is a SkinnedMeshRenderer and if the option in FBX Export settings is toggled on.
  1939. if (!ExportOptions.AnimateSkinnedMesh && (uniGO.GetComponent<SkinnedMeshRenderer> () != null)) {
  1940. continue;
  1941. }
  1942. FbxNode fbxNode;
  1943. if (!MapUnityObjectToFbxNode.TryGetValue(uniGO, out fbxNode))
  1944. {
  1945. Debug.LogError(string.Format("no FbxNode found for {0}", uniGO.name));
  1946. continue;
  1947. }
  1948. int index = QuaternionCurve.GetQuaternionIndex (propertyName);
  1949. if (index >= 0) {
  1950. // Rotation property; save it to convert quaternion -> euler later.
  1951. RotationCurve rotCurve = GetRotationCurve<QuaternionCurve> (uniGO, uniAnimClip.frameRate, ref rotations);
  1952. rotCurve.SetCurve (index, uniAnimCurve);
  1953. continue;
  1954. }
  1955. index = EulerCurve.GetEulerIndex (propertyName);
  1956. if (index >= 0) {
  1957. RotationCurve rotCurve = GetRotationCurve<EulerCurve> (uniGO, uniAnimClip.frameRate, ref rotations);
  1958. rotCurve.SetCurve (index, uniAnimCurve);
  1959. continue;
  1960. }
  1961. // simple property (e.g. intensity), export right away
  1962. ExportAnimationCurve (fbxNode, uniAnimCurve, uniAnimClip.frameRate,
  1963. propertyName, uniCurve.propertyType,
  1964. fbxAnimLayer);
  1965. }
  1966. }
  1967. // now export all the quaternion curves
  1968. foreach (var kvp in rotations) {
  1969. var unityGo = kvp.Key;
  1970. var rot = kvp.Value;
  1971. FbxNode fbxNode;
  1972. if (!MapUnityObjectToFbxNode.TryGetValue (unityGo, out fbxNode)) {
  1973. Debug.LogError (string.Format ("no FbxNode found for {0}", unityGo.name));
  1974. continue;
  1975. }
  1976. rot.Animate (unityGo.transform, fbxNode, fbxAnimLayer, Verbose);
  1977. }
  1978. }
  1979. /// <summary>
  1980. /// Transfers transform animation from source to dest. Replaces dest's Unity Animation Curves with updated animations.
  1981. /// NOTE: Source must be the parent of dest.
  1982. /// </summary>
  1983. /// <param name="source">Source animated object.</param>
  1984. /// <param name="dest">Destination, child of the source.</param>
  1985. /// <param name="sampleRate">Sample rate.</param>
  1986. /// <param name="unityCurves">Unity curves.</param>
  1987. [SecurityPermission(SecurityAction.LinkDemand)]
  1988. private void TransferMotion(Transform source, Transform dest, float sampleRate, ref Dictionary<GameObject, List<UnityCurve>> unityCurves){
  1989. // get sample times for curves in dest + source
  1990. // at each sample time, evaluate all 18 transfom anim curves, creating 2 transform matrices
  1991. // combine the matrices, get the new values, apply to the 9 new anim curves for dest
  1992. if (dest.parent != source) {
  1993. Debug.LogError ("dest must be a child of source");
  1994. return;
  1995. }
  1996. List<UnityCurve> sourceUnityCurves;
  1997. if (!unityCurves.TryGetValue (source.gameObject, out sourceUnityCurves)) {
  1998. return; // nothing to do, source has no animation
  1999. }
  2000. List<UnityCurve> destUnityCurves;
  2001. if (!unityCurves.TryGetValue (dest.gameObject, out destUnityCurves)) {
  2002. destUnityCurves = new List<UnityCurve> ();
  2003. }
  2004. List<AnimationCurve> animCurves = new List<AnimationCurve> ();
  2005. foreach (var curve in sourceUnityCurves) {
  2006. // TODO: check if curve is anim related
  2007. animCurves.Add(curve.uniAnimCurve);
  2008. }
  2009. foreach (var curve in destUnityCurves) {
  2010. animCurves.Add (curve.uniAnimCurve);
  2011. }
  2012. var sampleTimes = GetSampleTimes (animCurves.ToArray (), sampleRate);
  2013. // need to create 9 new UnityCurves, one for each property
  2014. var posKeyFrames = new Keyframe[3][];
  2015. var rotKeyFrames = new Keyframe[3][];
  2016. var scaleKeyFrames = new Keyframe[3][];
  2017. for (int k = 0; k < posKeyFrames.Length; k++) {
  2018. posKeyFrames [k] = new Keyframe[sampleTimes.Count];
  2019. rotKeyFrames[k] = new Keyframe[sampleTimes.Count];
  2020. scaleKeyFrames[k] = new Keyframe[sampleTimes.Count];
  2021. }
  2022. // If we have a point in local coords represented as a column-vector x, the equation of x in coordinates relative to source's parent is:
  2023. // x_grandparent = source * dest * x
  2024. // Now we're going to change dest to dest' which has the animation from source. And we're going to change
  2025. // source to source' which has no animation. The equation of x will become:
  2026. // x_grandparent = source' * dest' * x
  2027. // We're not changing x_grandparent and x, so we need that:
  2028. // source * dest = source' * dest'
  2029. // We know dest and source (both animated) and source' (static). Solve for dest':
  2030. // dest' = (source')^-1 * source * dest
  2031. int keyIndex = 0;
  2032. var sourceStaticMatrixInverse = Matrix4x4.TRS(source.localPosition, source.localRotation, source.localScale).inverse;
  2033. foreach (var currSampleTime in sampleTimes)
  2034. {
  2035. var sourceLocalMatrix = GetTransformMatrix (currSampleTime, source, sourceUnityCurves);
  2036. var destLocalMatrix = GetTransformMatrix (currSampleTime, dest, destUnityCurves);
  2037. var newLocalMatrix = sourceStaticMatrixInverse * sourceLocalMatrix * destLocalMatrix;
  2038. FbxVector4 translation, rotation, scale;
  2039. GetTRSFromMatrix (newLocalMatrix, out translation, out rotation, out scale);
  2040. // get rotation directly from matrix, as otherwise causes issues
  2041. // with negative rotations.
  2042. var rot = newLocalMatrix.rotation.eulerAngles;
  2043. for (int k = 0; k < 3; k++) {
  2044. posKeyFrames [k][keyIndex] = new Keyframe(currSampleTime, (float)translation [k]);
  2045. rotKeyFrames [k][keyIndex] = new Keyframe(currSampleTime, rot [k]);
  2046. scaleKeyFrames [k][keyIndex] = new Keyframe(currSampleTime, (float)scale [k]);
  2047. }
  2048. keyIndex++;
  2049. }
  2050. // create the new list of unity curves, and add it to dest's curves
  2051. var newUnityCurves = new List<UnityCurve>();
  2052. string posPropName = "m_LocalPosition.";
  2053. string rotPropName = "localEulerAnglesRaw.";
  2054. string scalePropName = "m_LocalScale.";
  2055. var xyz = "xyz";
  2056. for (int k = 0; k < 3; k++) {
  2057. var posUniCurve = new UnityCurve ( posPropName + xyz[k], new AnimationCurve(posKeyFrames[k]), typeof(Transform));
  2058. newUnityCurves.Add (posUniCurve);
  2059. var rotUniCurve = new UnityCurve ( rotPropName + xyz[k], new AnimationCurve(rotKeyFrames[k]), typeof(Transform));
  2060. newUnityCurves.Add (rotUniCurve);
  2061. var scaleUniCurve = new UnityCurve ( scalePropName + xyz[k], new AnimationCurve(scaleKeyFrames[k]), typeof(Transform));
  2062. newUnityCurves.Add (scaleUniCurve);
  2063. }
  2064. // remove old transform curves
  2065. RemoveTransformCurves (ref sourceUnityCurves);
  2066. RemoveTransformCurves (ref destUnityCurves);
  2067. unityCurves [source.gameObject] = sourceUnityCurves;
  2068. if (!unityCurves.ContainsKey(dest.gameObject)) {
  2069. unityCurves.Add (dest.gameObject, newUnityCurves);
  2070. return;
  2071. }
  2072. unityCurves [dest.gameObject].AddRange(newUnityCurves);
  2073. }
  2074. private void RemoveTransformCurves(ref List<UnityCurve> curves){
  2075. var transformCurves = new List<UnityCurve> ();
  2076. var transformPropNames = new string[]{"m_LocalPosition.", "m_LocalRotation", "localEulerAnglesRaw.", "m_LocalScale."};
  2077. foreach (var curve in curves) {
  2078. foreach (var prop in transformPropNames) {
  2079. if (curve.propertyName.StartsWith (prop)) {
  2080. transformCurves.Add (curve);
  2081. break;
  2082. }
  2083. }
  2084. }
  2085. foreach (var curve in transformCurves) {
  2086. curves.Remove (curve);
  2087. }
  2088. }
  2089. private Matrix4x4 GetTransformMatrix(float currSampleTime, Transform orig, List<UnityCurve> unityCurves){
  2090. var sourcePos = orig.localPosition;
  2091. var sourceRot = orig.localRotation;
  2092. var sourceScale = orig.localScale;
  2093. foreach (var uniCurve in unityCurves) {
  2094. float currSampleValue = uniCurve.uniAnimCurve.Evaluate(currSampleTime);
  2095. string propName = uniCurve.propertyName;
  2096. // try position, scale, quat then euler
  2097. int temp = QuaternionCurve.GetQuaternionIndex(propName);
  2098. if (temp >= 0) {
  2099. sourceRot [temp] = currSampleValue;
  2100. continue;
  2101. }
  2102. temp = EulerCurve.GetEulerIndex (propName);
  2103. if (temp >= 0) {
  2104. var euler = sourceRot.eulerAngles;
  2105. euler [temp] = currSampleValue;
  2106. sourceRot.eulerAngles = euler;
  2107. continue;
  2108. }
  2109. temp = GetPositionIndex (propName);
  2110. if (temp >= 0) {
  2111. sourcePos [temp] = currSampleValue;
  2112. continue;
  2113. }
  2114. temp = GetScaleIndex (propName);
  2115. if (temp >= 0) {
  2116. sourceScale [temp] = currSampleValue;
  2117. }
  2118. }
  2119. sourceRot = Quaternion.Euler(sourceRot.eulerAngles.x, sourceRot.eulerAngles.y, sourceRot.eulerAngles.z);
  2120. return Matrix4x4.TRS(sourcePos, sourceRot, sourceScale);
  2121. }
  2122. internal struct UnityCurve {
  2123. public string propertyName;
  2124. public AnimationCurve uniAnimCurve;
  2125. public System.Type propertyType;
  2126. public UnityCurve(string propertyName, AnimationCurve uniAnimCurve, System.Type propertyType){
  2127. this.propertyName = propertyName;
  2128. this.uniAnimCurve = uniAnimCurve;
  2129. this.propertyType = propertyType;
  2130. }
  2131. }
  2132. private int GetPositionIndex(string uniPropertyName){
  2133. System.StringComparison ct = System.StringComparison.CurrentCulture;
  2134. bool isPositionComponent = uniPropertyName.StartsWith ("m_LocalPosition.", ct);
  2135. if (!isPositionComponent) { return -1; }
  2136. switch (uniPropertyName [uniPropertyName.Length - 1]) {
  2137. case 'x':
  2138. return 0;
  2139. case 'y':
  2140. return 1;
  2141. case 'z':
  2142. return 2;
  2143. default:
  2144. return -1;
  2145. }
  2146. }
  2147. private int GetScaleIndex(string uniPropertyName){
  2148. System.StringComparison ct = System.StringComparison.CurrentCulture;
  2149. bool isScaleComponent = uniPropertyName.StartsWith ("m_LocalScale.", ct);
  2150. if (!isScaleComponent) { return -1; }
  2151. switch (uniPropertyName [uniPropertyName.Length - 1]) {
  2152. case 'x':
  2153. return 0;
  2154. case 'y':
  2155. return 1;
  2156. case 'z':
  2157. return 2;
  2158. default:
  2159. return -1;
  2160. }
  2161. }
  2162. /// <summary>
  2163. /// Gets or creates the rotation curve for GameObject uniGO.
  2164. /// </summary>
  2165. /// <returns>The rotation curve.</returns>
  2166. /// <param name="uniGO">Unity GameObject.</param>
  2167. /// <param name="frameRate">Frame rate.</param>
  2168. /// <param name="rotations">Rotations.</param>
  2169. /// <typeparam name="T"> RotationCurve is abstract so specify type of RotationCurve to create.</typeparam>
  2170. private RotationCurve GetRotationCurve<T>(
  2171. GameObject uniGO, float frameRate,
  2172. ref Dictionary<GameObject, RotationCurve> rotations
  2173. ) where T : RotationCurve, new()
  2174. {
  2175. RotationCurve rotCurve;
  2176. if (!rotations.TryGetValue (uniGO, out rotCurve)) {
  2177. rotCurve = new T { SampleRate = frameRate };
  2178. rotations.Add (uniGO, rotCurve);
  2179. }
  2180. return rotCurve;
  2181. }
  2182. /// <summary>
  2183. /// Export the Animator component on this game object
  2184. /// </summary>
  2185. [SecurityPermission(SecurityAction.LinkDemand)]
  2186. private void ExportAnimation (GameObject uniRoot, FbxScene fbxScene)
  2187. {
  2188. if (!uniRoot)
  2189. {
  2190. return;
  2191. }
  2192. var exportedClips = new HashSet<AnimationClip> ();
  2193. var uniAnimator = uniRoot.GetComponent<Animator> ();
  2194. if (uniAnimator)
  2195. {
  2196. // Try the animator controller (mecanim)
  2197. var controller = uniAnimator.runtimeAnimatorController;
  2198. if (controller)
  2199. {
  2200. // Only export each clip once per game object.
  2201. foreach (var clip in controller.animationClips) {
  2202. if (exportedClips.Add (clip)) {
  2203. ExportAnimationClip (clip, uniRoot, fbxScene);
  2204. }
  2205. }
  2206. }
  2207. }
  2208. // Try the playable director
  2209. var director = uniRoot.GetComponent<UnityEngine.Playables.PlayableDirector> ();
  2210. if (director)
  2211. {
  2212. Debug.LogWarning(string.Format("Exporting animation from PlayableDirector on {0} not supported", uniRoot.name));
  2213. // TODO: export animationclips from playabledirector
  2214. }
  2215. // Try the animation (legacy)
  2216. var uniAnimation = uniRoot.GetComponent<Animation> ();
  2217. if (uniAnimation)
  2218. {
  2219. // Only export each clip once per game object.
  2220. foreach (var uniAnimObj in uniAnimation) {
  2221. AnimationState uniAnimState = uniAnimObj as AnimationState;
  2222. if (uniAnimState)
  2223. {
  2224. AnimationClip uniAnimClip = uniAnimState.clip;
  2225. if (exportedClips.Add (uniAnimClip)) {
  2226. ExportAnimationClip (uniAnimClip, uniRoot, fbxScene);
  2227. }
  2228. }
  2229. }
  2230. }
  2231. }
  2232. /// <summary>
  2233. /// configures default camera for the scene
  2234. /// </summary>
  2235. private void SetDefaultCamera (FbxScene fbxScene)
  2236. {
  2237. if(fbxScene == null) { return; }
  2238. if (string.IsNullOrEmpty(DefaultCamera))
  2239. DefaultCamera = Globals.FBXSDK_CAMERA_PERSPECTIVE;
  2240. fbxScene.GetGlobalSettings ().SetDefaultCamera (DefaultCamera);
  2241. }
  2242. /// <summary>
  2243. /// Ensures that the inputted name is unique.
  2244. /// If a duplicate name is found, then it is incremented.
  2245. /// e.g. Sphere becomes Sphere_1
  2246. /// </summary>
  2247. /// <returns>Unique name</returns>
  2248. /// <param name="name">Name</param>
  2249. /// <param name="nameToCountMap">The dictionary to use to map name to # of occurences</param>
  2250. private string GetUniqueName(string name, Dictionary<string, int> nameToCountMap)
  2251. {
  2252. var uniqueName = name;
  2253. int count;
  2254. if (nameToCountMap.TryGetValue(name, out count))
  2255. {
  2256. uniqueName = string.Format(UniqueNameFormat, name, count);
  2257. }
  2258. else
  2259. {
  2260. count = 0;
  2261. }
  2262. nameToCountMap[name] = count + 1;
  2263. return uniqueName;
  2264. }
  2265. /// <summary>
  2266. /// Ensures that the inputted name is unique.
  2267. /// If a duplicate name is found, then it is incremented.
  2268. /// e.g. Sphere becomes Sphere_1
  2269. /// </summary>
  2270. /// <returns>Unique name</returns>
  2271. /// <param name="name">Name</param>
  2272. private string GetUniqueFbxNodeName(string name)
  2273. {
  2274. return GetUniqueName(name, NameToIndexMap);
  2275. }
  2276. /// <summary>
  2277. /// Ensures that the inputted material name is unique.
  2278. /// If a duplicate name is found, then it is incremented.
  2279. /// e.g. mat becomes mat_1
  2280. /// </summary>
  2281. /// <param name="name">Name</param>
  2282. /// <returns>Unique material name</returns>
  2283. private string GetUniqueMaterialName(string name)
  2284. {
  2285. return GetUniqueName(name, MaterialNameToIndexMap);
  2286. }
  2287. /// <summary>
  2288. /// Ensures that the inputted texture name is unique.
  2289. /// If a duplicate name is found, then it is incremented.
  2290. /// e.g. tex becomes tex_1
  2291. /// </summary>
  2292. /// <param name="name">Name</param>
  2293. /// <returns>Unique texture name</returns>
  2294. private string GetUniqueTextureName(string name)
  2295. {
  2296. return GetUniqueName(name, TextureNameToIndexMap);
  2297. }
  2298. /// <summary>
  2299. /// Create a fbxNode from unityGo.
  2300. /// </summary>
  2301. /// <param name="unityGo"></param>
  2302. /// <param name="fbxScene"></param>
  2303. /// <returns>the created FbxNode</returns>
  2304. private FbxNode CreateFbxNode(GameObject unityGo, FbxScene fbxScene)
  2305. {
  2306. string fbxName = unityGo.name;
  2307. if (ExportOptions.UseMayaCompatibleNames)
  2308. {
  2309. fbxName = ConvertToMayaCompatibleName(unityGo.name);
  2310. if (ExportOptions.AllowSceneModification)
  2311. {
  2312. unityGo.name = fbxName;
  2313. }
  2314. }
  2315. FbxNode fbxNode = FbxNode.Create(fbxScene, GetUniqueFbxNodeName(fbxName));
  2316. // Default inheritance type in FBX is RrSs, which causes scaling issues in Maya as
  2317. // both Maya and Unity use RSrs inheritance by default.
  2318. // Note: MotionBuilder uses RrSs inheritance by default as well, though it is possible
  2319. // to select a different inheritance type in the UI.
  2320. // Use RSrs as the scaling inheritance instead.
  2321. fbxNode.SetTransformationInheritType(FbxTransform.EInheritType.eInheritRSrs);
  2322. MapUnityObjectToFbxNode[unityGo] = fbxNode;
  2323. return fbxNode;
  2324. }
  2325. /// <summary>
  2326. /// Creates an FbxNode for each GameObject.
  2327. /// </summary>
  2328. /// <returns>The number of nodes exported.</returns>
  2329. internal int ExportTransformHierarchy(
  2330. GameObject unityGo, FbxScene fbxScene, FbxNode fbxNodeParent,
  2331. int exportProgress, int objectCount, Vector3 newCenter,
  2332. TransformExportType exportType = TransformExportType.Local,
  2333. ExportSettings.LODExportType lodExportType = ExportSettings.LODExportType.All
  2334. )
  2335. {
  2336. int numObjectsExported = exportProgress;
  2337. FbxNode fbxNode = CreateFbxNode(unityGo, fbxScene);
  2338. if (Verbose)
  2339. Debug.Log (string.Format ("exporting {0}", fbxNode.GetName ()));
  2340. numObjectsExported++;
  2341. if (EditorUtility.DisplayCancelableProgressBar (
  2342. ProgressBarTitle,
  2343. string.Format ("Creating FbxNode {0}/{1}", numObjectsExported, objectCount),
  2344. (numObjectsExported / (float)objectCount) * 0.25f)) {
  2345. // cancel silently
  2346. return -1;
  2347. }
  2348. ExportTransform (unityGo.transform, fbxNode, newCenter, exportType);
  2349. fbxNodeParent.AddChild (fbxNode);
  2350. // if this object has an LOD group, then export according to the LOD preference setting
  2351. var lodGroup = unityGo.GetComponent<LODGroup>();
  2352. if (lodGroup && lodExportType != ExportSettings.LODExportType.All) {
  2353. LOD[] lods = lodGroup.GetLODs ();
  2354. // LODs are ordered from highest to lowest.
  2355. // If exporting lowest LOD, reverse the array
  2356. if (lodExportType == ExportSettings.LODExportType.Lowest) {
  2357. // reverse the array
  2358. LOD[] tempLods = new LOD[lods.Length];
  2359. System.Array.Copy (lods, tempLods, lods.Length);
  2360. System.Array.Reverse (tempLods);
  2361. lods = tempLods;
  2362. }
  2363. for(int i = 0; i < lods.Length; i++){
  2364. var lod = lods [i];
  2365. bool exportedRenderer = false;
  2366. foreach (var renderer in lod.renderers) {
  2367. // only export if parented under LOD group
  2368. if (renderer.transform.parent == unityGo.transform) {
  2369. numObjectsExported = ExportTransformHierarchy (renderer.gameObject, fbxScene, fbxNode, numObjectsExported, objectCount, newCenter, lodExportType: lodExportType);
  2370. exportedRenderer = true;
  2371. } else if(Verbose) {
  2372. Debug.LogFormat ("FbxExporter: Not exporting LOD {0}: {1}", i, renderer.name);
  2373. }
  2374. }
  2375. // if at least one renderer for this LOD was exported, then we succeeded
  2376. // so stop exporting.
  2377. if (exportedRenderer) {
  2378. return numObjectsExported;
  2379. }
  2380. }
  2381. }
  2382. // now unityGo through our children and recurse
  2383. foreach (Transform childT in unityGo.transform) {
  2384. numObjectsExported = ExportTransformHierarchy (childT.gameObject, fbxScene, fbxNode, numObjectsExported, objectCount, newCenter, lodExportType: lodExportType);
  2385. }
  2386. return numObjectsExported;
  2387. }
  2388. /// <summary>
  2389. /// Exports all animation clips in the hierarchy along with
  2390. /// the minimum required GameObject information.
  2391. /// i.e. Animated GameObjects, their ancestors, and their transforms are exported,
  2392. /// but components are only exported if explicitly animated. Meshes are not exported.
  2393. /// </summary>
  2394. /// <returns>The number of nodes exported.</returns>
  2395. [SecurityPermission(SecurityAction.LinkDemand)]
  2396. internal int ExportAnimationOnly(
  2397. GameObject unityGO,
  2398. FbxScene fbxScene,
  2399. int exportProgress,
  2400. int objectCount,
  2401. Vector3 newCenter,
  2402. IExportData data,
  2403. TransformExportType exportType = TransformExportType.Local
  2404. ){
  2405. AnimationOnlyExportData exportData = (AnimationOnlyExportData)data;
  2406. int numObjectsExported = exportProgress;
  2407. // make sure anim destination node is exported as well
  2408. var exportSet = exportData.Objects;
  2409. if (ExportOptions.AnimationDest && ExportOptions.AnimationSource)
  2410. {
  2411. exportSet.Add(ExportOptions.AnimationDest.gameObject);
  2412. }
  2413. // first export all the animated bones that are in the export set
  2414. // as only a subset of bones are exported, but we still need to make sure the bone transforms are correct
  2415. if(!ExportAnimatedBones(unityGO, fbxScene, ref numObjectsExported, objectCount, exportData))
  2416. {
  2417. // export cancelled
  2418. return -1;
  2419. }
  2420. // export everything else and make sure all nodes are connected
  2421. foreach (var go in exportSet) {
  2422. FbxNode node;
  2423. if (!ExportGameObjectAndParents (
  2424. go, unityGO, fbxScene, out node, newCenter, exportType, ref numObjectsExported, objectCount
  2425. )) {
  2426. // export cancelled
  2427. return -1;
  2428. }
  2429. ExportConstraints(go, fbxScene, node);
  2430. System.Type compType;
  2431. if (exportData.exportComponent.TryGetValue (go, out compType)) {
  2432. if (compType == typeof(Light)) {
  2433. ExportLight (go, fbxScene, node);
  2434. } else if (compType == typeof(Camera)) {
  2435. ExportCamera (go, fbxScene, node);
  2436. }
  2437. }
  2438. }
  2439. return numObjectsExported;
  2440. }
  2441. internal class SkinnedMeshBoneInfo {
  2442. public SkinnedMeshRenderer skinnedMesh;
  2443. public Dictionary<Transform, int> boneDict;
  2444. public Dictionary<Transform, Matrix4x4> boneToBindPose;
  2445. public SkinnedMeshBoneInfo(SkinnedMeshRenderer skinnedMesh, Dictionary<Transform, int> boneDict){
  2446. this.skinnedMesh = skinnedMesh;
  2447. this.boneDict = boneDict;
  2448. this.boneToBindPose = new Dictionary<Transform, Matrix4x4>();
  2449. }
  2450. }
  2451. private bool ExportAnimatedBones (
  2452. GameObject unityGo,
  2453. FbxScene fbxScene,
  2454. ref int exportProgress,
  2455. int objectCount,
  2456. AnimationOnlyExportData exportData
  2457. )
  2458. {
  2459. var skinnedMeshRenderers = unityGo.GetComponentsInChildren<SkinnedMeshRenderer>();
  2460. foreach (var skinnedMesh in skinnedMeshRenderers)
  2461. {
  2462. var boneArray = skinnedMesh.bones;
  2463. var bones = new HashSet<GameObject>();
  2464. var boneDict = new Dictionary<Transform, int>();
  2465. for (int i = 0; i < boneArray.Length; i++)
  2466. {
  2467. bones.Add(boneArray[i].gameObject);
  2468. boneDict.Add(boneArray[i], i);
  2469. }
  2470. // get the bones that are also in the export set
  2471. bones.IntersectWith(exportData.Objects);
  2472. var boneInfo = new SkinnedMeshBoneInfo(skinnedMesh, boneDict);
  2473. foreach (var bone in bones)
  2474. {
  2475. FbxNode fbxNode;
  2476. // bone already exported
  2477. if (MapUnityObjectToFbxNode.TryGetValue(bone, out fbxNode))
  2478. {
  2479. continue;
  2480. }
  2481. fbxNode = CreateFbxNode(bone, fbxScene);
  2482. exportProgress++;
  2483. if (EditorUtility.DisplayCancelableProgressBar(
  2484. ProgressBarTitle,
  2485. string.Format("Creating FbxNode {0}/{1}", exportProgress, objectCount),
  2486. (exportProgress / (float)objectCount) * 0.5f))
  2487. {
  2488. // cancel silently
  2489. return false;
  2490. }
  2491. ExportBoneTransform(fbxNode, fbxScene, bone.transform, boneInfo);
  2492. }
  2493. }
  2494. return true;
  2495. }
  2496. /// <summary>
  2497. /// Exports the Gameobject and its ancestors.
  2498. /// </summary>
  2499. /// <returns><c>true</c>, if game object and parents were exported,
  2500. /// <c>false</c> if export cancelled.</returns>
  2501. private bool ExportGameObjectAndParents(
  2502. GameObject unityGo,
  2503. GameObject rootObject,
  2504. FbxScene fbxScene,
  2505. out FbxNode fbxNode,
  2506. Vector3 newCenter,
  2507. TransformExportType exportType,
  2508. ref int exportProgress,
  2509. int objectCount
  2510. )
  2511. {
  2512. // node doesn't exist so create it
  2513. if (!MapUnityObjectToFbxNode.TryGetValue(unityGo, out fbxNode))
  2514. {
  2515. fbxNode = CreateFbxNode(unityGo, fbxScene);
  2516. exportProgress++;
  2517. if (EditorUtility.DisplayCancelableProgressBar(
  2518. ProgressBarTitle,
  2519. string.Format("Creating FbxNode {0}/{1}", exportProgress, objectCount),
  2520. (exportProgress / (float)objectCount) * 0.5f))
  2521. {
  2522. // cancel silently
  2523. return false;
  2524. }
  2525. ExportTransform(unityGo.transform, fbxNode, newCenter, exportType);
  2526. }
  2527. if (unityGo == rootObject || unityGo.transform.parent == null)
  2528. {
  2529. fbxScene.GetRootNode().AddChild(fbxNode);
  2530. return true;
  2531. }
  2532. // make sure all the nodes are connected and exported
  2533. FbxNode fbxNodeParent;
  2534. if (!ExportGameObjectAndParents (
  2535. unityGo.transform.parent.gameObject,
  2536. rootObject,
  2537. fbxScene,
  2538. out fbxNodeParent,
  2539. newCenter,
  2540. TransformExportType.Local,
  2541. ref exportProgress,
  2542. objectCount
  2543. )) {
  2544. // export cancelled
  2545. return false;
  2546. }
  2547. fbxNodeParent.AddChild (fbxNode);
  2548. return true;
  2549. }
  2550. /// <summary>
  2551. /// Exports the bone transform.
  2552. /// </summary>
  2553. /// <returns><c>true</c>, if bone transform was exported, <c>false</c> otherwise.</returns>
  2554. /// <param name="fbxNode">Fbx node.</param>
  2555. /// <param name="fbxScene">Fbx scene.</param>
  2556. /// <param name="unityBone">Unity bone.</param>
  2557. /// <param name="boneInfo">Bone info.</param>
  2558. private bool ExportBoneTransform(
  2559. FbxNode fbxNode, FbxScene fbxScene, Transform unityBone, SkinnedMeshBoneInfo boneInfo
  2560. ){
  2561. if (boneInfo == null || boneInfo.skinnedMesh == null || boneInfo.boneDict == null || unityBone == null) {
  2562. return false;
  2563. }
  2564. var skinnedMesh = boneInfo.skinnedMesh;
  2565. var boneDict = boneInfo.boneDict;
  2566. var rootBone = skinnedMesh.rootBone;
  2567. // setup the skeleton
  2568. var fbxSkeleton = fbxNode.GetSkeleton ();
  2569. if (fbxSkeleton == null) {
  2570. fbxSkeleton = FbxSkeleton.Create (fbxScene, unityBone.name + SkeletonPrefix);
  2571. fbxSkeleton.Size.Set (1.0f * UnitScaleFactor);
  2572. fbxNode.SetNodeAttribute (fbxSkeleton);
  2573. }
  2574. var fbxSkeletonType = FbxSkeleton.EType.eLimbNode;
  2575. // Only set the rootbone's skeleton type to FbxSkeleton.EType.eRoot
  2576. // if it has at least one child that is also a bone.
  2577. // Otherwise if it is marked as Root but has no bones underneath,
  2578. // Maya will import it as a Null object instead of a bone.
  2579. if (rootBone == unityBone && rootBone.childCount > 0)
  2580. {
  2581. var hasChildBone = false;
  2582. foreach (Transform child in unityBone)
  2583. {
  2584. if (boneDict.ContainsKey(child))
  2585. {
  2586. hasChildBone = true;
  2587. break;
  2588. }
  2589. }
  2590. if (hasChildBone)
  2591. {
  2592. fbxSkeletonType = FbxSkeleton.EType.eRoot;
  2593. }
  2594. }
  2595. fbxSkeleton.SetSkeletonType (fbxSkeletonType);
  2596. var bindPoses = skinnedMesh.sharedMesh.bindposes;
  2597. // get bind pose
  2598. Matrix4x4 bindPose;
  2599. if (!boneInfo.boneToBindPose.TryGetValue (unityBone, out bindPose)) {
  2600. bindPose = GetBindPose (unityBone, bindPoses, boneDict, skinnedMesh);
  2601. boneInfo.boneToBindPose.Add (unityBone, bindPose);
  2602. }
  2603. Matrix4x4 pose;
  2604. // get parent's bind pose
  2605. Matrix4x4 parentBindPose;
  2606. if (!boneInfo.boneToBindPose.TryGetValue (unityBone.parent, out parentBindPose)) {
  2607. parentBindPose = GetBindPose (unityBone.parent, bindPoses, boneDict, skinnedMesh);
  2608. boneInfo.boneToBindPose.Add (unityBone.parent, parentBindPose);
  2609. }
  2610. pose = parentBindPose * bindPose.inverse;
  2611. FbxVector4 translation, rotation, scale;
  2612. GetTRSFromMatrix (pose, out translation, out rotation, out scale);
  2613. // Export bones with zero rotation, using a pivot instead to set the rotation
  2614. // so that the bones are easier to animate and the rotation shows up as the "joint orientation" in Maya.
  2615. fbxNode.LclTranslation.Set (new FbxDouble3(-translation.X*UnitScaleFactor, translation.Y*UnitScaleFactor, translation.Z*UnitScaleFactor));
  2616. fbxNode.LclRotation.Set (new FbxDouble3(0,0,0));
  2617. fbxNode.LclScaling.Set (new FbxDouble3 (scale.X, scale.Y, scale.Z));
  2618. // TODO (UNI-34294): add detailed comment about why we export rotation as pre-rotation
  2619. fbxNode.SetRotationActive (true);
  2620. fbxNode.SetPivotState (FbxNode.EPivotSet.eSourcePivot, FbxNode.EPivotState.ePivotReference);
  2621. fbxNode.SetPreRotation (FbxNode.EPivotSet.eSourcePivot, new FbxVector4 (rotation.X, -rotation.Y, -rotation.Z));
  2622. return true;
  2623. }
  2624. private void GetTRSFromMatrix(Matrix4x4 unityMatrix, out FbxVector4 translation, out FbxVector4 rotation, out FbxVector4 scale){
  2625. // FBX is transposed relative to Unity: transpose as we convert.
  2626. FbxMatrix matrix = new FbxMatrix ();
  2627. matrix.SetColumn (0, new FbxVector4 (unityMatrix.GetRow (0).x, unityMatrix.GetRow (0).y, unityMatrix.GetRow (0).z, unityMatrix.GetRow (0).w));
  2628. matrix.SetColumn (1, new FbxVector4 (unityMatrix.GetRow (1).x, unityMatrix.GetRow (1).y, unityMatrix.GetRow (1).z, unityMatrix.GetRow (1).w));
  2629. matrix.SetColumn (2, new FbxVector4 (unityMatrix.GetRow (2).x, unityMatrix.GetRow (2).y, unityMatrix.GetRow (2).z, unityMatrix.GetRow (2).w));
  2630. matrix.SetColumn (3, new FbxVector4 (unityMatrix.GetRow (3).x, unityMatrix.GetRow (3).y, unityMatrix.GetRow (3).z, unityMatrix.GetRow (3).w));
  2631. // FBX wants translation, rotation (in euler angles) and scale.
  2632. // We assume there's no real shear, just rounding error.
  2633. FbxVector4 shear;
  2634. double sign;
  2635. matrix.GetElements (out translation, out rotation, out shear, out scale, out sign);
  2636. }
  2637. /// <summary>
  2638. /// Counts how many objects are between this object and the root (exclusive).
  2639. /// </summary>
  2640. /// <returns>The object to root count.</returns>
  2641. /// <param name="startObject">Start object.</param>
  2642. /// <param name="root">Root object.</param>
  2643. private static int GetObjectToRootDepth(Transform startObject, Transform root){
  2644. if (startObject == null) {
  2645. return 0;
  2646. }
  2647. int count = 0;
  2648. var parent = startObject.parent;
  2649. while (parent != null && parent != root) {
  2650. count++;
  2651. parent = parent.parent;
  2652. }
  2653. return count;
  2654. }
  2655. /// <summary>
  2656. /// Gets the count of animated objects to be exported.
  2657. ///
  2658. /// In addition, collects the minimum set of what needs to be exported for each GameObject hierarchy.
  2659. /// This contains all the animated GameObjects, their ancestors, their transforms, as well as any animated
  2660. /// components and the animation clips. Also, the first animation to export, if any.
  2661. /// </summary>
  2662. /// <returns>The animation only hierarchy count.</returns>
  2663. /// <param name="exportSet">GameObject hierarchies selected for export.</param>
  2664. /// <param name="hierarchyToExportData">Map from GameObject hierarchy to animation export data.</param>
  2665. [SecurityPermission(SecurityAction.LinkDemand)]
  2666. internal int GetAnimOnlyHierarchyCount(Dictionary<GameObject, IExportData> hierarchyToExportData)
  2667. {
  2668. // including any parents of animated objects that are exported
  2669. var completeExpSet = new HashSet<GameObject>();
  2670. foreach (var data in hierarchyToExportData.Values) {
  2671. foreach (var go in data.Objects) {
  2672. completeExpSet.Add(go);
  2673. var parent = go.transform.parent;
  2674. while (parent != null && completeExpSet.Add(parent.gameObject)) {
  2675. parent = parent.parent;
  2676. }
  2677. }
  2678. }
  2679. return completeExpSet.Count;
  2680. }
  2681. [SecurityPermission(SecurityAction.LinkDemand)]
  2682. internal static Dictionary<GameObject, IExportData> GetExportData(Object[] objects, IExportOptions exportOptions = null)
  2683. {
  2684. if (exportOptions==null)
  2685. exportOptions = DefaultOptions;
  2686. Debug.Assert(exportOptions!=null);
  2687. Dictionary<GameObject, IExportData> exportData = new Dictionary<GameObject, IExportData>();
  2688. if (exportOptions.ModelAnimIncludeOption == ExportSettings.Include.Model)
  2689. {
  2690. return null;
  2691. }
  2692. foreach (var obj in objects)
  2693. {
  2694. GameObject go = ModelExporter.GetGameObject (obj);
  2695. if (go)
  2696. {
  2697. exportData[go] = GetExportData(go, exportOptions);
  2698. }
  2699. else if (IsEditorClip(obj))
  2700. {
  2701. KeyValuePair<GameObject, AnimationClip> pair = AnimationOnlyExportData.GetGameObjectAndAnimationClip(obj);
  2702. exportData[pair.Key] = GetExportData (pair.Key, pair.Value, exportOptions);
  2703. }
  2704. }
  2705. return exportData.Count == 0 ? null : exportData;
  2706. }
  2707. [SecurityPermission(SecurityAction.LinkDemand)]
  2708. internal static IExportData GetExportData(GameObject rootObject, AnimationClip animationClip, IExportOptions exportOptions = null)
  2709. {
  2710. if (exportOptions==null)
  2711. exportOptions = DefaultOptions;
  2712. Debug.Assert(exportOptions!=null);
  2713. var exportData = new AnimationOnlyExportData();
  2714. exportData.CollectDependencies(animationClip, rootObject, exportOptions);
  2715. // could not find any dependencies, return null
  2716. if(exportData.Objects.Count <= 0)
  2717. {
  2718. return null;
  2719. }
  2720. return exportData;
  2721. }
  2722. internal static IExportData GetExportData(GameObject go, IExportOptions exportOptions = null)
  2723. {
  2724. if (exportOptions==null)
  2725. exportOptions = DefaultOptions;
  2726. Debug.Assert(exportOptions!=null);
  2727. // gather all animation clips
  2728. var legacyAnim = go.GetComponentsInChildren<Animation>();
  2729. var genericAnim = go.GetComponentsInChildren<Animator>();
  2730. var exportData = new AnimationOnlyExportData();
  2731. int depthFromRootAnimation = int.MaxValue;
  2732. Animation rootAnimation = null;
  2733. foreach (var anim in legacyAnim)
  2734. {
  2735. int count = GetObjectToRootDepth(anim.transform, go.transform);
  2736. if (count < depthFromRootAnimation)
  2737. {
  2738. depthFromRootAnimation = count;
  2739. rootAnimation = anim;
  2740. }
  2741. var animClips = AnimationUtility.GetAnimationClips(anim.gameObject);
  2742. exportData.CollectDependencies(animClips, anim.gameObject, exportOptions);
  2743. }
  2744. int depthFromRootAnimator = int.MaxValue;
  2745. Animator rootAnimator = null;
  2746. foreach (var anim in genericAnim)
  2747. {
  2748. int count = GetObjectToRootDepth(anim.transform, go.transform);
  2749. if (count < depthFromRootAnimator)
  2750. {
  2751. depthFromRootAnimator = count;
  2752. rootAnimator = anim;
  2753. }
  2754. // Try the animator controller (mecanim)
  2755. var controller = anim.runtimeAnimatorController;
  2756. if (controller)
  2757. {
  2758. exportData.CollectDependencies(controller.animationClips, anim.gameObject, exportOptions);
  2759. }
  2760. }
  2761. // set the first clip to export
  2762. if (depthFromRootAnimation < depthFromRootAnimator)
  2763. {
  2764. exportData.defaultClip = rootAnimation.clip;
  2765. }
  2766. else if(rootAnimator)
  2767. {
  2768. // Try the animator controller (mecanim)
  2769. var controller = rootAnimator.runtimeAnimatorController;
  2770. if (controller)
  2771. {
  2772. var dController = controller as UnityEditor.Animations.AnimatorController;
  2773. if (dController && dController.layers.Count() > 0)
  2774. {
  2775. var motion = dController.layers[0].stateMachine.defaultState.motion;
  2776. var defaultClip = motion as AnimationClip;
  2777. if (defaultClip)
  2778. {
  2779. exportData.defaultClip = defaultClip;
  2780. }
  2781. else
  2782. {
  2783. Debug.LogWarningFormat("Couldn't export motion {0}", motion.name);
  2784. }
  2785. }
  2786. }
  2787. }
  2788. return exportData;
  2789. }
  2790. /// <summary>
  2791. /// Export components on this game object.
  2792. /// Transform components have already been exported.
  2793. /// This function exports the other components and animation.
  2794. /// </summary>
  2795. [SecurityPermission(SecurityAction.LinkDemand)]
  2796. private bool ExportComponents(FbxScene fbxScene)
  2797. {
  2798. var animationNodes = new HashSet<GameObject> ();
  2799. int numObjectsExported = 0;
  2800. int objectCount = MapUnityObjectToFbxNode.Count;
  2801. foreach (KeyValuePair<GameObject, FbxNode> entry in MapUnityObjectToFbxNode) {
  2802. numObjectsExported++;
  2803. if (EditorUtility.DisplayCancelableProgressBar (
  2804. ProgressBarTitle,
  2805. string.Format ("Exporting Components for GameObject {0}/{1}", numObjectsExported, objectCount),
  2806. ((numObjectsExported / (float)objectCount) * 0.25f) + 0.25f)) {
  2807. // cancel silently
  2808. return false;
  2809. }
  2810. var unityGo = entry.Key;
  2811. var fbxNode = entry.Value;
  2812. // try export mesh
  2813. bool exportedMesh = ExportInstance (unityGo, fbxScene, fbxNode);
  2814. if (!exportedMesh) {
  2815. exportedMesh = ExportMesh (unityGo, fbxNode);
  2816. }
  2817. // export camera, but only if no mesh was exported
  2818. bool exportedCamera = false;
  2819. if (!exportedMesh) {
  2820. exportedCamera = ExportCamera (unityGo, fbxScene, fbxNode);
  2821. }
  2822. // export light, but only if no mesh or camera was exported
  2823. if (!exportedMesh && !exportedCamera) {
  2824. ExportLight (unityGo, fbxScene, fbxNode);
  2825. }
  2826. ExportConstraints(unityGo, fbxScene, fbxNode);
  2827. }
  2828. return true;
  2829. }
  2830. /// <summary>
  2831. /// Checks if the GameObject has animation.
  2832. /// </summary>
  2833. /// <returns><c>true</c>, if object has animation, <c>false</c> otherwise.</returns>
  2834. /// <param name="go">Go.</param>
  2835. private bool GameObjectHasAnimation(GameObject go){
  2836. return go != null &&
  2837. (go.GetComponent<Animator> () ||
  2838. go.GetComponent<Animation> () ||
  2839. go.GetComponent<UnityEngine.Playables.PlayableDirector> ());
  2840. }
  2841. /// <summary>
  2842. /// A count of how many GameObjects we are exporting, to have a rough
  2843. /// idea of how long creating the scene will take.
  2844. /// </summary>
  2845. /// <returns>The hierarchy count.</returns>
  2846. /// <param name="exportSet">Export set.</param>
  2847. internal int GetHierarchyCount (HashSet<GameObject> exportSet)
  2848. {
  2849. int count = 0;
  2850. Queue<GameObject> queue = new Queue<GameObject> (exportSet);
  2851. while (queue.Count > 0) {
  2852. var obj = queue.Dequeue ();
  2853. var objTransform = obj.transform;
  2854. foreach (Transform child in objTransform) {
  2855. queue.Enqueue (child.gameObject);
  2856. }
  2857. count++;
  2858. }
  2859. return count;
  2860. }
  2861. /// <summary>
  2862. /// Removes objects that will already be exported anyway.
  2863. /// E.g. if a parent and its child are both selected, then the child
  2864. /// will be removed from the export set.
  2865. /// </summary>
  2866. /// <returns>The revised export set</returns>
  2867. /// <param name="unityExportSet">Unity export set.</param>
  2868. [SecurityPermission(SecurityAction.LinkDemand)]
  2869. internal static HashSet<GameObject> RemoveRedundantObjects(IEnumerable<UnityEngine.Object> unityExportSet)
  2870. {
  2871. // basically just remove the descendents from the unity export set
  2872. HashSet<GameObject> toExport = new HashSet<GameObject> ();
  2873. HashSet<UnityEngine.Object> hashedExportSet = new HashSet<Object> (unityExportSet);
  2874. foreach(var obj in unityExportSet){
  2875. var unityGo = GetGameObject (obj);
  2876. if (unityGo) {
  2877. // if any of this nodes ancestors is already in the export set,
  2878. // then ignore it, it will get exported already
  2879. bool parentInSet = false;
  2880. var parent = unityGo.transform.parent;
  2881. while (parent != null) {
  2882. if (hashedExportSet.Contains (parent.gameObject)) {
  2883. parentInSet = true;
  2884. break;
  2885. }
  2886. parent = parent.parent;
  2887. }
  2888. if (!parentInSet) {
  2889. toExport.Add (unityGo);
  2890. }
  2891. }
  2892. }
  2893. return toExport;
  2894. }
  2895. /// <summary>
  2896. /// Recursively go through the hierarchy, unioning the bounding box centers
  2897. /// of all the children, to find the combined bounds.
  2898. /// </summary>
  2899. /// <param name="t">Transform.</param>
  2900. /// <param name="boundsUnion">The Bounds that is the Union of all the bounds on this transform's hierarchy.</param>
  2901. private static void EncapsulateBounds(Transform t, ref Bounds boundsUnion)
  2902. {
  2903. var bounds = GetBounds (t);
  2904. boundsUnion.Encapsulate (bounds);
  2905. foreach (Transform child in t) {
  2906. EncapsulateBounds (child, ref boundsUnion);
  2907. }
  2908. }
  2909. /// <summary>
  2910. /// Gets the bounds of a transform.
  2911. /// Looks first at the Renderer, then Mesh, then Collider.
  2912. /// Default to a bounds with center transform.position and size zero.
  2913. /// </summary>
  2914. /// <returns>The bounds.</returns>
  2915. /// <param name="t">Transform.</param>
  2916. private static Bounds GetBounds(Transform t)
  2917. {
  2918. var renderer = t.GetComponent<Renderer> ();
  2919. if (renderer) {
  2920. return renderer.bounds;
  2921. }
  2922. var mesh = t.GetComponent<Mesh> ();
  2923. if (mesh) {
  2924. return mesh.bounds;
  2925. }
  2926. var collider = t.GetComponent<Collider> ();
  2927. if (collider) {
  2928. return collider.bounds;
  2929. }
  2930. return new Bounds(t.position, Vector3.zero);
  2931. }
  2932. /// <summary>
  2933. /// Finds the center of a group of GameObjects.
  2934. /// </summary>
  2935. /// <returns>Center of gameObjects.</returns>
  2936. /// <param name="gameObjects">Game objects.</param>
  2937. internal static Vector3 FindCenter(IEnumerable<GameObject> gameObjects)
  2938. {
  2939. Bounds bounds = new Bounds();
  2940. // Assign the initial bounds to first GameObject's bounds
  2941. // (if we initialize the bounds to 0, then 0 will be part of the bounds)
  2942. foreach (var go in gameObjects) {
  2943. var tempBounds = GetBounds (go.transform);
  2944. bounds = new Bounds (tempBounds.center, tempBounds.size);
  2945. break;
  2946. }
  2947. foreach (var go in gameObjects) {
  2948. EncapsulateBounds (go.transform, ref bounds);
  2949. }
  2950. return bounds.center;
  2951. }
  2952. /// <summary>
  2953. /// Gets the recentered translation.
  2954. /// </summary>
  2955. /// <returns>The recentered translation.</returns>
  2956. /// <param name="t">Transform.</param>
  2957. /// <param name="center">Center point.</param>
  2958. internal static Vector3 GetRecenteredTranslation(Transform t, Vector3 center)
  2959. {
  2960. return t.position - center;
  2961. }
  2962. internal enum TransformExportType { Local, Global, Reset };
  2963. /// <summary>
  2964. /// Export all the objects in the set.
  2965. /// Return the number of objects in the set that we exported.
  2966. ///
  2967. /// This refreshes the asset database.
  2968. /// </summary>
  2969. [SecurityPermission(SecurityAction.LinkDemand)]
  2970. internal int ExportAll (
  2971. IEnumerable<UnityEngine.Object> unityExportSet,
  2972. Dictionary<GameObject, IExportData> exportData)
  2973. {
  2974. exportCancelled = false;
  2975. m_lastFilePath = LastFilePath;
  2976. // Export first to a temporary file
  2977. // in case the export is cancelled.
  2978. // This way we won't overwrite existing files.
  2979. try
  2980. {
  2981. // create a temp file in the same directory where the fbx will be exported
  2982. var exportDir = Path.GetDirectoryName(m_lastFilePath);
  2983. var lastFileName = Path.GetFileName(m_lastFilePath);
  2984. var tempFileName = Path.GetFileNameWithoutExtension(Path.GetRandomFileName()) + "_" + lastFileName;
  2985. m_tempFilePath = Path.Combine(new string[] { exportDir, tempFileName });
  2986. }
  2987. catch(IOException){
  2988. return 0;
  2989. }
  2990. if (string.IsNullOrEmpty (m_tempFilePath)) {
  2991. return 0;
  2992. }
  2993. try {
  2994. bool animOnly = exportData != null && ExportOptions.ModelAnimIncludeOption == ExportSettings.Include.Anim;
  2995. bool status = false;
  2996. // Create the FBX manager
  2997. using (var fbxManager = FbxManager.Create ()) {
  2998. // Configure fbx IO settings.
  2999. fbxManager.SetIOSettings (FbxIOSettings.Create (fbxManager, Globals.IOSROOT));
  3000. // Create the exporter
  3001. var fbxExporter = FbxExporter.Create (fbxManager, "Exporter");
  3002. // Initialize the exporter.
  3003. // fileFormat must be binary if we are embedding textures
  3004. int fileFormat = -1;
  3005. if (ExportOptions.ExportFormat == ExportSettings.ExportFormat.ASCII)
  3006. {
  3007. fileFormat = fbxManager.GetIOPluginRegistry().FindWriterIDByDescription("FBX ascii (*.fbx)");
  3008. }
  3009. status = fbxExporter.Initialize (m_tempFilePath, fileFormat, fbxManager.GetIOSettings ());
  3010. // Check that initialization of the fbxExporter was successful
  3011. if (!status)
  3012. return 0;
  3013. // Set compatibility to 2014
  3014. fbxExporter.SetFileExportVersion ("FBX201400");
  3015. // Set the progress callback.
  3016. fbxExporter.SetProgressCallback (ExportProgressCallback);
  3017. // Create a scene
  3018. var fbxScene = FbxScene.Create (fbxManager, "Scene");
  3019. // set up the scene info
  3020. FbxDocumentInfo fbxSceneInfo = FbxDocumentInfo.Create (fbxManager, "SceneInfo");
  3021. fbxSceneInfo.mTitle = Title;
  3022. fbxSceneInfo.mSubject = Subject;
  3023. fbxSceneInfo.mAuthor = "Unity Technologies";
  3024. fbxSceneInfo.mRevision = "1.0";
  3025. fbxSceneInfo.mKeywords = Keywords;
  3026. fbxSceneInfo.mComment = Comments;
  3027. fbxSceneInfo.Original_ApplicationName.Set(string.Format("Unity {0}", PACKAGE_UI_NAME));
  3028. // set last saved to be the same as original, as this is a new file.
  3029. fbxSceneInfo.LastSaved_ApplicationName.Set(fbxSceneInfo.Original_ApplicationName.Get());
  3030. var version = GetVersionFromReadme();
  3031. if(version != null){
  3032. fbxSceneInfo.Original_ApplicationVersion.Set(version);
  3033. fbxSceneInfo.LastSaved_ApplicationVersion.Set(fbxSceneInfo.Original_ApplicationVersion.Get());
  3034. }
  3035. fbxScene.SetSceneInfo (fbxSceneInfo);
  3036. // Set up the axes (Y up, Z forward, X to the right) and units (centimeters)
  3037. // Exporting in centimeters as this is the default unit for FBX files, and easiest
  3038. // to work with when importing into Maya or Max
  3039. var fbxSettings = fbxScene.GetGlobalSettings ();
  3040. fbxSettings.SetSystemUnit (FbxSystemUnit.cm);
  3041. // The Unity axis system has Y up, Z forward, X to the right (left handed system with odd parity).
  3042. // The Maya axis system has Y up, Z forward, X to the left (right handed system with odd parity).
  3043. // We need to export right-handed for Maya because ConvertScene can't switch handedness:
  3044. // https://forums.autodesk.com/t5/fbx-forum/get-confused-with-fbxaxissystem-convertscene/td-p/4265472
  3045. fbxSettings.SetAxisSystem (FbxAxisSystem.MayaYUp);
  3046. // export set of object
  3047. FbxNode fbxRootNode = fbxScene.GetRootNode ();
  3048. // stores how many objects we have exported, -1 if export was cancelled
  3049. int exportProgress = 0;
  3050. IEnumerable<GameObject> revisedExportSet = null;
  3051. // Total # of objects to be exported
  3052. // Used by progress bar to show how many objects will be exported in total
  3053. // i.e. exporting x/count...
  3054. int count = 0;
  3055. // number of object hierarchies being exported.
  3056. // Used to figure out exported transforms for root objects.
  3057. // i.e. if we are exporting a single hierarchy at local position, then it's root is set to zero,
  3058. // but if we are exporting multiple hierarchies at local position, then each hierarchy will be recentered according
  3059. // to the center of the bounding box.
  3060. int rootObjCount = 0;
  3061. if(animOnly){
  3062. count = GetAnimOnlyHierarchyCount(exportData);
  3063. revisedExportSet = from entry in exportData select entry.Key;
  3064. rootObjCount = exportData.Keys.Count;
  3065. } else {
  3066. var revisedGOSet = RemoveRedundantObjects(unityExportSet);
  3067. count = GetHierarchyCount (revisedGOSet);
  3068. rootObjCount = revisedGOSet.Count;
  3069. revisedExportSet = revisedGOSet;
  3070. }
  3071. if(count <= 0){
  3072. // nothing to export
  3073. Debug.LogWarning("Nothing to Export");
  3074. return 0;
  3075. }
  3076. Vector3 center = Vector3.zero;
  3077. TransformExportType transformExportType = TransformExportType.Global;
  3078. switch(ExportOptions.ObjectPosition){
  3079. case ExportSettings.ObjectPosition.LocalCentered:
  3080. // one object to export -> move to (0,0,0)
  3081. if(rootObjCount == 1){
  3082. var tempList = new List<GameObject>(revisedExportSet);
  3083. center = tempList[0].transform.position;
  3084. break;
  3085. }
  3086. // more than one object to export -> get bounding center
  3087. center = FindCenter(revisedExportSet);
  3088. break;
  3089. case ExportSettings.ObjectPosition.Reset:
  3090. transformExportType = TransformExportType.Reset;
  3091. break;
  3092. // absolute center -> don't do anything
  3093. default:
  3094. center = Vector3.zero;
  3095. break;
  3096. }
  3097. foreach (var unityGo in revisedExportSet) {
  3098. IExportData data;
  3099. if(animOnly && exportData.TryGetValue(unityGo, out data)){
  3100. exportProgress = this.ExportAnimationOnly(unityGo, fbxScene, exportProgress, count, center, data, transformExportType);
  3101. }
  3102. else {
  3103. exportProgress = this.ExportTransformHierarchy (unityGo, fbxScene, fbxRootNode,
  3104. exportProgress, count, center, transformExportType, ExportOptions.LODExportType);
  3105. }
  3106. if (exportCancelled || exportProgress < 0) {
  3107. Debug.LogWarning ("Export Cancelled");
  3108. return 0;
  3109. }
  3110. }
  3111. if(!animOnly){
  3112. if(!ExportComponents(fbxScene)){
  3113. Debug.LogWarning ("Export Cancelled");
  3114. return 0;
  3115. }
  3116. }
  3117. // Export animation if any
  3118. if (exportData != null)
  3119. {
  3120. foreach (var unityGo in revisedExportSet)
  3121. {
  3122. IExportData iData;
  3123. if (!exportData.TryGetValue(unityGo, out iData))
  3124. {
  3125. continue;
  3126. }
  3127. var data = iData as AnimationOnlyExportData;
  3128. if (data == null)
  3129. {
  3130. Debug.LogWarningFormat("FBX Exporter: no animation export data found for {0}", unityGo.name);
  3131. continue;
  3132. }
  3133. // export animation
  3134. // export default clip first
  3135. if (data.defaultClip != null)
  3136. {
  3137. var defaultClip = data.defaultClip;
  3138. ExportAnimationClip(defaultClip, data.animationClips[defaultClip], fbxScene);
  3139. data.animationClips.Remove(defaultClip);
  3140. }
  3141. foreach (var animClip in data.animationClips)
  3142. {
  3143. ExportAnimationClip(animClip.Key, animClip.Value, fbxScene);
  3144. }
  3145. }
  3146. }
  3147. // Set the scene's default camera.
  3148. SetDefaultCamera (fbxScene);
  3149. // Export the scene to the file.
  3150. status = fbxExporter.Export (fbxScene);
  3151. // cleanup
  3152. fbxScene.Destroy ();
  3153. fbxExporter.Destroy ();
  3154. }
  3155. if (exportCancelled) {
  3156. Debug.LogWarning ("Export Cancelled");
  3157. return 0;
  3158. }
  3159. // make a temporary copy of the original metafile
  3160. string originalMetafilePath = "";
  3161. if (ExportOptions.PreserveImportSettings && File.Exists(m_lastFilePath))
  3162. {
  3163. originalMetafilePath = SaveMetafile();
  3164. }
  3165. // delete old file, move temp file
  3166. ReplaceFile();
  3167. AssetDatabase.Refresh();
  3168. // replace with original metafile if specified to
  3169. if (ExportOptions.PreserveImportSettings && !string.IsNullOrEmpty(originalMetafilePath))
  3170. {
  3171. ReplaceMetafile(originalMetafilePath);
  3172. }
  3173. return status == true ? NumNodes : 0;
  3174. }
  3175. finally {
  3176. // You must clear the progress bar when you're done,
  3177. // otherwise it never goes away and many actions in Unity
  3178. // are blocked (e.g. you can't quit).
  3179. EditorUtility.ClearProgressBar ();
  3180. // make sure the temp file is deleted, no matter
  3181. // when we return
  3182. DeleteTempFile();
  3183. }
  3184. }
  3185. static bool exportCancelled = false;
  3186. static bool ExportProgressCallback (float percentage, string status)
  3187. {
  3188. // Convert from percentage to [0,1].
  3189. // Then convert from that to [0.5,1] because the first half of
  3190. // the progress bar was for creating the scene.
  3191. var progress01 = 0.5f * (1f + (percentage / 100.0f));
  3192. bool cancel = EditorUtility.DisplayCancelableProgressBar (ProgressBarTitle, "Exporting Scene...", progress01);
  3193. if (cancel) {
  3194. exportCancelled = true;
  3195. }
  3196. // Unity says "true" for "cancel"; FBX wants "true" for "continue"
  3197. return !cancel;
  3198. }
  3199. /// <summary>
  3200. /// Deletes the file that got created while exporting.
  3201. /// </summary>
  3202. private void DeleteTempFile ()
  3203. {
  3204. if (!File.Exists (m_tempFilePath)) {
  3205. return;
  3206. }
  3207. try {
  3208. File.Delete (m_tempFilePath);
  3209. } catch (IOException) {
  3210. }
  3211. if (File.Exists (m_tempFilePath)) {
  3212. Debug.LogWarning ("Failed to delete file: " + m_tempFilePath);
  3213. }
  3214. }
  3215. /// <summary>
  3216. /// Replaces the file we are overwriting with
  3217. /// the temp file that was exported to.
  3218. /// </summary>
  3219. private void ReplaceFile ()
  3220. {
  3221. if (m_tempFilePath.Equals (m_lastFilePath) || !File.Exists (m_tempFilePath)) {
  3222. return;
  3223. }
  3224. // delete old file
  3225. try {
  3226. File.Delete (m_lastFilePath);
  3227. } catch (IOException) {
  3228. }
  3229. // refresh the database so Unity knows the file's been deleted
  3230. AssetDatabase.Refresh();
  3231. if (File.Exists (m_lastFilePath)) {
  3232. Debug.LogWarning ("Failed to delete file: " + m_lastFilePath);
  3233. }
  3234. // rename the new file
  3235. try{
  3236. File.Move(m_tempFilePath, m_lastFilePath);
  3237. } catch(IOException){
  3238. Debug.LogWarning (string.Format("Failed to move file {0} to {1}", m_tempFilePath, m_lastFilePath));
  3239. }
  3240. }
  3241. private string SaveMetafile()
  3242. {
  3243. var tempMetafilePath = Path.GetTempFileName();
  3244. // Try as an absolute path
  3245. var fbxPath = m_lastFilePath;
  3246. if (AssetDatabase.LoadAssetAtPath(fbxPath, typeof(Object)) == null)
  3247. {
  3248. // Try as a relative path
  3249. fbxPath = "Assets" + m_lastFilePath.Substring(Application.dataPath.Length);
  3250. if (AssetDatabase.LoadAssetAtPath(fbxPath, typeof(Object)) == null)
  3251. {
  3252. Debug.LogWarning(string.Format("Failed to find a valid asset at {0}. Import settings will be reset to default values.", m_lastFilePath));
  3253. return "";
  3254. }
  3255. }
  3256. // get metafile for original fbx file
  3257. var metafile = fbxPath + ".meta";
  3258. #if UNITY_2019_1_OR_NEWER
  3259. metafile = VersionControl.Provider.GetAssetByPath(fbxPath).metaPath;
  3260. #endif
  3261. // save it to a temp file
  3262. try {
  3263. File.Copy(metafile, tempMetafilePath, true);
  3264. } catch(IOException) {
  3265. Debug.LogWarning (string.Format("Failed to copy file {0} to {1}. Import settings will be reset to default values.", metafile, tempMetafilePath));
  3266. return "";
  3267. }
  3268. return tempMetafilePath;
  3269. }
  3270. private void ReplaceMetafile(string metafilePath)
  3271. {
  3272. // Try as an absolute path
  3273. var fbxPath = m_lastFilePath;
  3274. if (AssetDatabase.LoadAssetAtPath(fbxPath, typeof(Object)) == null)
  3275. {
  3276. // Try as a relative path
  3277. fbxPath = "Assets" + m_lastFilePath.Substring(Application.dataPath.Length);
  3278. if (AssetDatabase.LoadAssetAtPath(fbxPath, typeof(Object)) == null)
  3279. {
  3280. Debug.LogWarning(string.Format("Failed to find a valid asset at {0}. Import settings will be reset to default values.", m_lastFilePath));
  3281. return;
  3282. }
  3283. }
  3284. // get metafile for new fbx file
  3285. var metafile = fbxPath + ".meta";
  3286. #if UNITY_2019_1_OR_NEWER
  3287. metafile = VersionControl.Provider.GetAssetByPath(fbxPath).metaPath;
  3288. #endif
  3289. // replace metafile with original one in temp file
  3290. try {
  3291. File.Copy(metafilePath, metafile, true);
  3292. } catch(IOException) {
  3293. Debug.LogWarning (string.Format("Failed to copy file {0} to {1}. Import settings will be reset to default values.", metafilePath, m_lastFilePath));
  3294. }
  3295. }
  3296. /// <summary>
  3297. /// GameObject/Export Selected Timeline Clip...
  3298. /// </summary>
  3299. /// <param name="command"></param>
  3300. [MenuItem(TimelineClipMenuItemName, false, 31)]
  3301. static void OnClipContextClick(MenuCommand command)
  3302. {
  3303. Object[] selectedObjects = Selection.objects;
  3304. foreach (Object editorClipSelected in selectedObjects)
  3305. {
  3306. // export first selected editor clip.
  3307. if (IsEditorClip(editorClipSelected)) {
  3308. ExportSingleTimelineClip(editorClipSelected);
  3309. return;
  3310. }
  3311. }
  3312. }
  3313. /// <summary>
  3314. /// Validate the menu item defined by the function OnClipContextClick.
  3315. /// </summary>
  3316. [MenuItem(TimelineClipMenuItemName, true, 31)]
  3317. static bool ValidateOnClipContextClick()
  3318. {
  3319. Object[] selectedObjects = Selection.objects;
  3320. foreach (Object editorClipSelected in selectedObjects)
  3321. {
  3322. if (IsEditorClip(editorClipSelected))
  3323. {
  3324. return true;
  3325. }
  3326. }
  3327. return false;
  3328. }
  3329. internal static bool IsEditorClip(object obj)
  3330. {
  3331. if (obj == null)
  3332. return false;
  3333. return obj.GetType().Name.Contains("EditorClip");
  3334. }
  3335. internal static void ExportSingleTimelineClip(Object editorClipSelected)
  3336. {
  3337. UnityEngine.Object[] exportArray = new UnityEngine.Object[] { editorClipSelected };
  3338. ExportModelEditorWindow.Init (exportArray, AnimationOnlyExportData.GetFileName(editorClipSelected), isTimelineAnim: true);
  3339. }
  3340. /// <summary>
  3341. /// Add a menu item "Export Model..." to a GameObject's context menu.
  3342. /// </summary>
  3343. /// <param name="command">Command.</param>
  3344. [MenuItem (MenuItemName, false, 30)]
  3345. static void OnContextItem (MenuCommand command)
  3346. {
  3347. if (Selection.objects.Length <= 0) {
  3348. DisplayNoSelectionDialog ();
  3349. return;
  3350. }
  3351. OnExport ();
  3352. }
  3353. /// <summary>
  3354. /// Validate the menu item defined by the function OnContextItem.
  3355. /// </summary>
  3356. [MenuItem (MenuItemName, true, 30)]
  3357. internal static bool OnValidateMenuItem ()
  3358. {
  3359. return true;
  3360. }
  3361. internal static void DisplayNoSelectionDialog()
  3362. {
  3363. UnityEditor.EditorUtility.DisplayDialog (
  3364. string.Format("{0} Warning", PACKAGE_UI_NAME),
  3365. "No GameObjects selected for export.",
  3366. "Ok");
  3367. }
  3368. //
  3369. // export mesh info from Unity
  3370. //
  3371. ///<summary>
  3372. ///Information about the mesh that is important for exporting.
  3373. ///</summary>
  3374. internal class MeshInfo
  3375. {
  3376. public Mesh mesh;
  3377. /// <summary>
  3378. /// Return true if there's a valid mesh information
  3379. /// </summary>
  3380. public bool IsValid { get { return mesh; } }
  3381. /// <summary>
  3382. /// Gets the vertex count.
  3383. /// </summary>
  3384. /// <value>The vertex count.</value>
  3385. public int VertexCount { get { return mesh.vertexCount; } }
  3386. /// <summary>
  3387. /// Gets the triangles. Each triangle is represented as 3 indices from the vertices array.
  3388. /// Ex: if triangles = [3,4,2], then we have one triangle with vertices vertices[3], vertices[4], and vertices[2]
  3389. /// </summary>
  3390. /// <value>The triangles.</value>
  3391. private int[] m_triangles;
  3392. public int [] Triangles { get {
  3393. if(m_triangles == null) { m_triangles = mesh.triangles; }
  3394. return m_triangles;
  3395. } }
  3396. /// <summary>
  3397. /// Gets the vertices, represented in local coordinates.
  3398. /// </summary>
  3399. /// <value>The vertices.</value>
  3400. private Vector3[] m_vertices;
  3401. public Vector3 [] Vertices { get {
  3402. if(m_vertices == null) { m_vertices = mesh.vertices; }
  3403. return m_vertices;
  3404. } }
  3405. /// <summary>
  3406. /// Gets the normals for the vertices.
  3407. /// </summary>
  3408. /// <value>The normals.</value>
  3409. private Vector3[] m_normals;
  3410. public Vector3 [] Normals { get {
  3411. if (m_normals == null) {
  3412. m_normals = mesh.normals;
  3413. }
  3414. return m_normals;
  3415. }
  3416. }
  3417. /// <summary>
  3418. /// Gets the binormals for the vertices.
  3419. /// </summary>
  3420. /// <value>The normals.</value>
  3421. private Vector3[] m_Binormals;
  3422. public Vector3 [] Binormals {
  3423. get {
  3424. /// NOTE: LINQ
  3425. /// return mesh.normals.Zip (mesh.tangents, (first, second)
  3426. /// => Math.cross (normal, tangent.xyz) * tangent.w
  3427. if (m_Binormals == null || m_Binormals.Length == 0)
  3428. {
  3429. var normals = Normals;
  3430. var tangents = Tangents;
  3431. if (HasValidNormals() && HasValidTangents()) {
  3432. m_Binormals = new Vector3 [normals.Length];
  3433. for (int i = 0; i < normals.Length; i++)
  3434. m_Binormals [i] = Vector3.Cross (normals [i],
  3435. tangents [i])
  3436. * tangents [i].w;
  3437. }
  3438. }
  3439. return m_Binormals;
  3440. }
  3441. }
  3442. /// <summary>
  3443. /// Gets the tangents for the vertices.
  3444. /// </summary>
  3445. /// <value>The tangents.</value>
  3446. private Vector4[] m_tangents;
  3447. public Vector4 [] Tangents { get {
  3448. if (m_tangents == null) {
  3449. m_tangents = mesh.tangents;
  3450. }
  3451. return m_tangents;
  3452. }
  3453. }
  3454. /// <summary>
  3455. /// Gets the vertex colors for the vertices.
  3456. /// </summary>
  3457. /// <value>The vertex colors.</value>
  3458. private Color32 [] m_vertexColors;
  3459. public Color32 [] VertexColors { get {
  3460. if (m_vertexColors == null) {
  3461. m_vertexColors = mesh.colors32;
  3462. }
  3463. return m_vertexColors;
  3464. }
  3465. }
  3466. /// <summary>
  3467. /// Gets the uvs.
  3468. /// </summary>
  3469. /// <value>The uv.</value>
  3470. private Vector2[] m_UVs;
  3471. public Vector2 [] UV { get {
  3472. if (m_UVs == null) {
  3473. m_UVs = mesh.uv;
  3474. }
  3475. return m_UVs;
  3476. }
  3477. }
  3478. /// <summary>
  3479. /// The material(s) used.
  3480. /// Always at least one.
  3481. /// None are missing materials (we replace missing materials with the default material).
  3482. /// </summary>
  3483. public Material[] Materials { get ; private set; }
  3484. private BoneWeight[] m_boneWeights;
  3485. public BoneWeight[] BoneWeights { get {
  3486. if (m_boneWeights == null) {
  3487. m_boneWeights = mesh.boneWeights;
  3488. }
  3489. return m_boneWeights;
  3490. }
  3491. }
  3492. /// <summary>
  3493. /// Set up the MeshInfo with the given mesh and materials.
  3494. /// </summary>
  3495. public MeshInfo (Mesh mesh, Material[] materials)
  3496. {
  3497. this.mesh = mesh;
  3498. this.m_Binormals = null;
  3499. this.m_vertices = null;
  3500. this.m_triangles = null;
  3501. this.m_normals = null;
  3502. this.m_UVs = null;
  3503. this.m_vertexColors = null;
  3504. this.m_tangents = null;
  3505. if (materials == null) {
  3506. this.Materials = new Material[] { DefaultMaterial };
  3507. } else {
  3508. this.Materials = materials.Select (mat => mat ? mat : DefaultMaterial).ToArray ();
  3509. if (this.Materials.Length == 0) {
  3510. this.Materials = new Material[] { DefaultMaterial };
  3511. }
  3512. }
  3513. }
  3514. public bool HasValidNormals(){
  3515. return Normals != null && Normals.Length > 0;
  3516. }
  3517. public bool HasValidBinormals(){
  3518. return HasValidNormals () &&
  3519. HasValidTangents () &&
  3520. Binormals != null;
  3521. }
  3522. public bool HasValidTangents(){
  3523. return Tangents != null && Tangents.Length > 0;
  3524. }
  3525. public bool HasValidVertexColors(){
  3526. return VertexColors != null && VertexColors.Length > 0;
  3527. }
  3528. }
  3529. /// <summary>
  3530. /// Get the GameObject
  3531. /// </summary>
  3532. internal static GameObject GetGameObject (Object obj)
  3533. {
  3534. if (obj is UnityEngine.Transform) {
  3535. var xform = obj as UnityEngine.Transform;
  3536. return xform.gameObject;
  3537. } else if (obj is UnityEngine.GameObject) {
  3538. return obj as UnityEngine.GameObject;
  3539. } else if (obj is Behaviour) {
  3540. var behaviour = obj as Behaviour;
  3541. return behaviour.gameObject;
  3542. }
  3543. return null;
  3544. }
  3545. /// <summary>
  3546. /// Map from type (must be a MonoBehaviour) to callback.
  3547. /// The type safety is lost; the caller must ensure it at run-time.
  3548. /// </summary>
  3549. static Dictionary<System.Type, GetMeshForComponent> MeshForComponentCallbacks
  3550. = new Dictionary<System.Type, GetMeshForComponent>();
  3551. /// <summary>
  3552. /// Register a callback to invoke if the object has a component of type T.
  3553. ///
  3554. /// This function is prefered over the other mesh callback
  3555. /// registration methods because it's type-safe, efficient, and
  3556. /// invocation order between types can be controlled in the UI by
  3557. /// reordering the components.
  3558. ///
  3559. /// It's an error to register a callback for a component that
  3560. /// already has one, unless 'replace' is set to true.
  3561. /// </summary>
  3562. internal static void RegisterMeshCallback<T>(GetMeshForComponent<T> callback, bool replace = false)
  3563. where T: UnityEngine.MonoBehaviour
  3564. {
  3565. // Under the hood we lose type safety, but don't let the user notice!
  3566. RegisterMeshCallback (typeof(T),
  3567. (ModelExporter exporter, MonoBehaviour component, FbxNode fbxNode) =>
  3568. callback (exporter, (T)component, fbxNode),
  3569. replace);
  3570. }
  3571. /// <summary>
  3572. /// Register a callback to invoke if the object has a component of type T.
  3573. ///
  3574. /// The callback will be invoked with an argument of type T, it's
  3575. /// safe to downcast.
  3576. ///
  3577. /// Normally you'll want to use the generic form, but this one is
  3578. /// easier to use with reflection.
  3579. /// </summary>
  3580. internal static void RegisterMeshCallback(System.Type t,
  3581. GetMeshForComponent callback,
  3582. bool replace = false)
  3583. {
  3584. if (!t.IsSubclassOf(typeof(MonoBehaviour))) {
  3585. throw new ModelExportException("Registering a callback for a type that isn't derived from MonoBehaviour: " + t);
  3586. }
  3587. if (!replace && MeshForComponentCallbacks.ContainsKey(t)) {
  3588. throw new ModelExportException("Replacing a callback for type " + t);
  3589. }
  3590. MeshForComponentCallbacks[t] = callback;
  3591. }
  3592. /// <summary>
  3593. /// Forget the callback linked to a component of type T.
  3594. /// </summary>
  3595. internal static void UnRegisterMeshCallback<T>()
  3596. {
  3597. MeshForComponentCallbacks.Remove(typeof(T));
  3598. }
  3599. /// <summary>
  3600. /// Forget the callback linked to a component of type T.
  3601. /// </summary>
  3602. internal static void UnRegisterMeshCallback(System.Type t)
  3603. {
  3604. MeshForComponentCallbacks.Remove(t);
  3605. }
  3606. /// <summary>
  3607. /// Forget the callbacks linked to components.
  3608. /// </summary>
  3609. internal static void UnRegisterAllMeshCallbacks()
  3610. {
  3611. MeshForComponentCallbacks.Clear();
  3612. }
  3613. static List<GetMeshForObject> MeshForObjectCallbacks = new List<GetMeshForObject>();
  3614. /// <summary>
  3615. /// Register a callback to invoke on every GameObject we export.
  3616. ///
  3617. /// Avoid doing this if you can use a callback that depends on type.
  3618. ///
  3619. /// The GameObject-based callbacks are checked before the
  3620. /// component-based ones.
  3621. ///
  3622. /// Multiple GameObject-based callbacks can be registered; they are
  3623. /// checked in order of registration.
  3624. /// </summary>
  3625. internal static void RegisterMeshObjectCallback(GetMeshForObject callback)
  3626. {
  3627. MeshForObjectCallbacks.Add(callback);
  3628. }
  3629. /// <summary>
  3630. /// Forget a GameObject-based callback.
  3631. /// </summary>
  3632. internal static void UnRegisterMeshObjectCallback(GetMeshForObject callback)
  3633. {
  3634. MeshForObjectCallbacks.Remove(callback);
  3635. }
  3636. /// <summary>
  3637. /// Forget all GameObject-based callbacks.
  3638. /// </summary>
  3639. internal static void UnRegisterAllMeshObjectCallbacks()
  3640. {
  3641. MeshForObjectCallbacks.Clear();
  3642. }
  3643. /// <summary>
  3644. /// Exports a mesh for a unity gameObject.
  3645. ///
  3646. /// This goes through the callback system to find the right mesh and
  3647. /// allow plugins to substitute their own meshes.
  3648. /// </summary>
  3649. [SecurityPermission(SecurityAction.LinkDemand)]
  3650. bool ExportMesh (GameObject gameObject, FbxNode fbxNode)
  3651. {
  3652. // First allow the object-based callbacks to have a hack at it.
  3653. foreach(var callback in MeshForObjectCallbacks) {
  3654. if (callback(this, gameObject, fbxNode)) {
  3655. return true;
  3656. }
  3657. }
  3658. // Next iterate over components and allow the component-based
  3659. // callbacks to have a hack at it. This is complicated by the
  3660. // potential of subclassing. While we're iterating we keep the
  3661. // first MeshFilter or SkinnedMeshRenderer we find.
  3662. Component defaultComponent = null;
  3663. foreach(var component in gameObject.GetComponents<Component>()) {
  3664. if (!component) {
  3665. continue;
  3666. }
  3667. var monoBehaviour = component as MonoBehaviour;
  3668. if (!monoBehaviour) {
  3669. // Check for default handling. But don't commit yet.
  3670. if (defaultComponent) {
  3671. continue;
  3672. } else if (component is MeshFilter) {
  3673. defaultComponent = component;
  3674. } else if (component is SkinnedMeshRenderer) {
  3675. defaultComponent = component;
  3676. }
  3677. } else {
  3678. // Check if we have custom behaviour for this component type, or
  3679. // one of its base classes.
  3680. if (!monoBehaviour.enabled) {
  3681. continue;
  3682. }
  3683. var componentType = monoBehaviour.GetType ();
  3684. do {
  3685. GetMeshForComponent callback;
  3686. if (MeshForComponentCallbacks.TryGetValue (componentType, out callback)) {
  3687. if (callback (this, monoBehaviour, fbxNode)) {
  3688. return true;
  3689. }
  3690. }
  3691. componentType = componentType.BaseType;
  3692. } while(componentType.IsSubclassOf (typeof(MonoBehaviour)));
  3693. }
  3694. }
  3695. // If we're here, custom handling didn't work.
  3696. // Revert to default handling.
  3697. // if user doesn't want to export mesh colliders, and this gameobject doesn't have a renderer
  3698. // then don't export it.
  3699. if (!ExportOptions.ExportUnrendered && (!gameObject.GetComponent<Renderer>() || !gameObject.GetComponent<Renderer>().enabled)) {
  3700. return false;
  3701. }
  3702. var meshFilter = defaultComponent as MeshFilter;
  3703. if (meshFilter) {
  3704. var renderer = gameObject.GetComponent<Renderer>();
  3705. var materials = renderer ? renderer.sharedMaterials : null;
  3706. return ExportMesh(new MeshInfo(meshFilter.sharedMesh, materials), fbxNode);
  3707. } else {
  3708. var smr = defaultComponent as SkinnedMeshRenderer;
  3709. if (smr) {
  3710. var result = ExportSkinnedMesh (gameObject, fbxNode.GetScene (), fbxNode);
  3711. if(!result){
  3712. // fall back to exporting as a static mesh
  3713. var mesh = new Mesh();
  3714. smr.BakeMesh(mesh);
  3715. var materials = smr.sharedMaterials;
  3716. result = ExportMesh(new MeshInfo(mesh, materials), fbxNode);
  3717. Object.DestroyImmediate(mesh);
  3718. }
  3719. return result;
  3720. }
  3721. }
  3722. return false;
  3723. }
  3724. /// <summary>
  3725. /// Number of nodes exported including siblings and decendents
  3726. /// </summary>
  3727. internal int NumNodes { get { return MapUnityObjectToFbxNode.Count; } }
  3728. /// <summary>
  3729. /// Number of meshes exported
  3730. /// </summary>
  3731. internal int NumMeshes { set; get; }
  3732. /// <summary>
  3733. /// Number of triangles exported
  3734. /// </summary>
  3735. internal int NumTriangles { set; get; }
  3736. /// <summary>
  3737. /// Cleans up this class on garbage collection
  3738. /// </summary>
  3739. public void Dispose ()
  3740. {
  3741. System.GC.SuppressFinalize(this);
  3742. }
  3743. internal bool Verbose { get { return ExportSettings.instance.VerboseProperty; } }
  3744. /// <summary>
  3745. /// manage the selection of a filename
  3746. /// </summary>
  3747. static string LastFilePath { get; set; }
  3748. private string m_tempFilePath { get; set; }
  3749. private string m_lastFilePath { get; set; }
  3750. const string kFBXFileExtension = "fbx";
  3751. private static string MakeFileName (string basename = "test", string extension = kFBXFileExtension)
  3752. {
  3753. return basename + "." + extension;
  3754. }
  3755. private static void OnExport ()
  3756. {
  3757. GameObject [] selectedGOs = Selection.GetFiltered<GameObject> (SelectionMode.TopLevel);
  3758. var toExport = ModelExporter.RemoveRedundantObjects(selectedGOs);
  3759. ExportModelEditorWindow.Init (System.Linq.Enumerable.Cast<UnityEngine.Object> (toExport), isTimelineAnim: false);
  3760. }
  3761. [SecurityPermission(SecurityAction.LinkDemand)]
  3762. internal static string ExportObject (
  3763. string filePath,
  3764. UnityEngine.Object root,
  3765. IExportOptions exportOptions = null
  3766. )
  3767. {
  3768. return ExportObjects(filePath, new Object[] { root }, exportOptions);
  3769. }
  3770. /// <summary>
  3771. /// Exports an array of Unity GameObjects to an FBX file.
  3772. /// </summary>
  3773. /// <returns>
  3774. /// The FBX file path if successful; otherwise returns null.
  3775. /// </returns>
  3776. /// <param name="filePath">Absolute file path to use for the FBX file.</param>
  3777. /// <param name="objects">Array of Unity GameObjects to export.</param>
  3778. [SecurityPermission(SecurityAction.LinkDemand)]
  3779. public static string ExportObjects(string filePath, UnityEngine.Object[] objects = null)
  3780. {
  3781. return ExportObjects(filePath, objects, exportOptions: null, exportData: null);
  3782. }
  3783. /// <summary>
  3784. /// Exports a single Unity GameObject to an FBX file.
  3785. /// </summary>
  3786. /// <returns>
  3787. /// The FBX file path if successful; otherwise null.
  3788. /// </returns>
  3789. /// <param name="filePath">Absolute file path to use for the FBX file.</param>
  3790. /// <param name="singleObject">The Unity GameObject to export.</param>
  3791. [SecurityPermission(SecurityAction.LinkDemand)]
  3792. public static string ExportObject (string filePath, UnityEngine.Object singleObject)
  3793. {
  3794. return ExportObjects(filePath, new Object[] {singleObject}, exportOptions: null);
  3795. }
  3796. /// <summary>
  3797. /// Exports a list of GameObjects to an FBX file.
  3798. /// <para>
  3799. /// Use the SaveFile panel to allow the user to enter a file name.
  3800. /// </para>
  3801. /// </summary>
  3802. [SecurityPermission(SecurityAction.LinkDemand)]
  3803. internal static string ExportObjects (
  3804. string filePath,
  3805. UnityEngine.Object[] objects = null,
  3806. IExportOptions exportOptions = null,
  3807. Dictionary<GameObject, IExportData> exportData = null
  3808. )
  3809. {
  3810. LastFilePath = filePath;
  3811. using (var fbxExporter = Create ()) {
  3812. // ensure output directory exists
  3813. EnsureDirectory (filePath);
  3814. fbxExporter.ExportOptions = exportOptions;
  3815. if (objects == null) {
  3816. objects = Selection.objects;
  3817. }
  3818. if (exportData==null)
  3819. exportData = ModelExporter.GetExportData (objects, exportOptions);
  3820. if (fbxExporter.ExportAll (objects, exportData) > 0) {
  3821. string message = string.Format ("Successfully exported: {0}", filePath);
  3822. UnityEngine.Debug.Log (message);
  3823. return filePath;
  3824. }
  3825. }
  3826. return null;
  3827. }
  3828. private static void EnsureDirectory (string path)
  3829. {
  3830. //check to make sure the path exists, and if it doesn't then
  3831. //create all the missing directories.
  3832. FileInfo fileInfo = new FileInfo (path);
  3833. if (!fileInfo.Exists) {
  3834. Directory.CreateDirectory (fileInfo.Directory.FullName);
  3835. }
  3836. }
  3837. /// <summary>
  3838. /// Removes the diacritics (i.e. accents) from letters.
  3839. /// e.g. é becomes e
  3840. /// </summary>
  3841. /// <returns>Text with accents removed.</returns>
  3842. /// <param name="text">Text.</param>
  3843. private static string RemoveDiacritics(string text)
  3844. {
  3845. var normalizedString = text.Normalize(System.Text.NormalizationForm.FormD);
  3846. var stringBuilder = new System.Text.StringBuilder();
  3847. foreach (var c in normalizedString)
  3848. {
  3849. var unicodeCategory = System.Globalization.CharUnicodeInfo.GetUnicodeCategory(c);
  3850. if (unicodeCategory != System.Globalization.UnicodeCategory.NonSpacingMark)
  3851. {
  3852. stringBuilder.Append(c);
  3853. }
  3854. }
  3855. return stringBuilder.ToString().Normalize(System.Text.NormalizationForm.FormC);
  3856. }
  3857. private static string ConvertToMayaCompatibleName(string name)
  3858. {
  3859. string newName = RemoveDiacritics (name);
  3860. if (char.IsDigit (newName [0])) {
  3861. newName = newName.Insert (0, InvalidCharReplacement.ToString());
  3862. }
  3863. for (int i = 0; i < newName.Length; i++) {
  3864. if (!char.IsLetterOrDigit (newName, i)) {
  3865. if (i < newName.Length-1 && newName [i] == MayaNamespaceSeparator) {
  3866. continue;
  3867. }
  3868. newName = newName.Replace (newName [i], InvalidCharReplacement);
  3869. }
  3870. }
  3871. return newName;
  3872. }
  3873. internal static string ConvertToValidFilename(string filename)
  3874. {
  3875. return System.Text.RegularExpressions.Regex.Replace (filename,
  3876. RegexCharStart + new string(Path.GetInvalidFileNameChars()) + RegexCharEnd,
  3877. InvalidCharReplacement.ToString()
  3878. );
  3879. }
  3880. }
  3881. }