tsShapeConstruct.cpp 124 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "platform/platform.h"
  23. #include "ts/tsShapeConstruct.h"
  24. #include "ts/tsShapeInstance.h"
  25. #include "ts/tsMaterialList.h"
  26. #include "console/consoleTypes.h"
  27. #include "console/engineAPI.h"
  28. #include "core/resourceManager.h"
  29. #include "core/stream/bitStream.h"
  30. #include "core/stream/fileStream.h"
  31. #include "core/stream/memStream.h"
  32. #include "core/fileObject.h"
  33. #define MAX_PATH_LENGTH 256
  34. //#define DEBUG_SPEW
  35. ConsoleDocClass(TSShapeConstructor,
  36. "@brief An object used to modify a DTS or COLLADA shape model after it has "
  37. "been loaded by Torque\n\n"
  38. "@ingroup gameObjects\n"
  39. );
  40. IMPLEMENT_CALLBACK(TSShapeConstructor, onLoad, void, (), (),
  41. "Called immediately after the DTS or DAE file has been loaded; before the "
  42. "shape data is available to any other object (StaticShape, Player etc). This "
  43. "is where you should put any post-load commands to modify the shape in-memory "
  44. "such as addNode, renameSequence etc.")
  45. IMPLEMENT_CALLBACK(TSShapeConstructor, onUnload, void, (), (),
  46. "Called when the DTS or DAE resource is flushed from memory. Not normally "
  47. "required, but may be useful to perform cleanup.")
  48. ImplementEnumType(TSShapeConstructorUpAxis,
  49. "Axis to use for upwards direction when importing from Collada.\n\n"
  50. "@ingroup TSShapeConstructor")
  51. {
  52. UPAXISTYPE_X_UP, "X_AXIS"
  53. },
  54. { UPAXISTYPE_Y_UP, "Y_AXIS" },
  55. { UPAXISTYPE_Z_UP, "Z_AXIS" },
  56. { UPAXISTYPE_COUNT, "DEFAULT" }
  57. EndImplementEnumType;
  58. ImplementEnumType(TSShapeConstructorLodType,
  59. "\n\n"
  60. "@ingroup TSShapeConstructor")
  61. {
  62. ColladaUtils::ImportOptions::DetectDTS, "DetectDTS"
  63. },
  64. { ColladaUtils::ImportOptions::SingleSize, "SingleSize" },
  65. { ColladaUtils::ImportOptions::TrailingNumber, "TrailingNumber" },
  66. EndImplementEnumType;
  67. ImplementEnumType(TSShapeConstructorAnimType,
  68. "\n\n"
  69. "@ingroup TSShapeConstructor")
  70. {
  71. ColladaUtils::ImportOptions::FrameCount, "Frames"
  72. },
  73. { ColladaUtils::ImportOptions::Seconds, "Seconds" },
  74. { ColladaUtils::ImportOptions::Milliseconds, "Milliseconds" },
  75. EndImplementEnumType;
  76. //-----------------------------------------------------------------------------
  77. String TSShapeConstructor::smCapsuleShapePath("tools/shapes/unit_capsule.dts");
  78. String TSShapeConstructor::smCubeShapePath("tools/shapes/unit_cube.dts");
  79. String TSShapeConstructor::smSphereShapePath("tools/shapes/unit_sphere.dts");
  80. ResourceRegisterPostLoadSignal< TSShape > TSShapeConstructor::_smAutoLoad(&TSShapeConstructor::_onTSShapeLoaded);
  81. ResourceRegisterUnloadSignal< TSShape > TSShapeConstructor::_smAutoUnload(&TSShapeConstructor::_onTSShapeUnloaded);
  82. void TSShapeConstructor::_onTSShapeLoaded(Resource< TSShape >& resource)
  83. {
  84. TSShapeConstructor* ctor = findShapeConstructorByFilename(resource.getPath().getFullPath());
  85. if (ctor)
  86. ctor->_onLoad(resource);
  87. if (ctor && ctor->mShape && ctor->mShape->needsReinit())
  88. {
  89. ctor->mShape->init();
  90. }
  91. }
  92. void TSShapeConstructor::_onTSShapeUnloaded(const Torque::Path& path, TSShape* shape)
  93. {
  94. TSShapeConstructor* ctor = findShapeConstructorByFilename(path.getFullPath());
  95. if (ctor && (ctor->getShape() == shape))
  96. ctor->_onUnload();
  97. }
  98. // TSShape names are case insensitive
  99. static inline bool namesEqual(const String& nameA, const String& nameB)
  100. {
  101. return nameA.equal(nameB, String::NoCase);
  102. }
  103. static void SplitSequencePathAndName(String& srcPath, String& srcName)
  104. {
  105. srcName = "";
  106. // Determine if there is a sequence name at the end of the source string, and
  107. // if so, split the filename from the sequence name
  108. S32 split = srcPath.find(' ', 0, String::Right);
  109. S32 split2 = srcPath.find('\t', 0, String::Right);
  110. if ((split == String::NPos) || (split2 > split))
  111. split = split2;
  112. if (split != String::NPos)
  113. {
  114. split2 = split + 1;
  115. while ((srcPath[split2] != '\0') && dIsspace(srcPath[split2]))
  116. split2++;
  117. // now 'split' is at the end of the path, and 'split2' is at the start of the sequence name
  118. srcName = srcPath.substr(split2);
  119. srcPath = srcPath.erase(split, srcPath.length() - split);
  120. }
  121. }
  122. //-----------------------------------------------------------------------------
  123. IMPLEMENT_CONOBJECT(TSShapeConstructor);
  124. TSShapeConstructor::TSShapeConstructor()
  125. : mLoadingShape(false)
  126. {
  127. mShapeAssetId = StringTable->EmptyString();
  128. mShapeAsset = StringTable->EmptyString();
  129. mOptions.upAxis = UPAXISTYPE_COUNT;
  130. mOptions.unit = -1.0f;
  131. mOptions.lodType = ColladaUtils::ImportOptions::TrailingNumber;
  132. mOptions.singleDetailSize = 2;
  133. mOptions.matNamePrefix = "";
  134. mOptions.alwaysImport = "";
  135. mOptions.neverImport = String(Con::getVariable("$TSShapeConstructor::neverImport"));
  136. mOptions.alwaysImportMesh = "";
  137. mOptions.neverImportMesh = String(Con::getVariable("$TSShapeConstructor::neverImportMesh"));
  138. mOptions.neverImportMat = String(Con::getVariable("$TSShapeConstructor::neverImportMat"));
  139. mOptions.ignoreNodeScale = false;
  140. mOptions.adjustCenter = false;
  141. mOptions.adjustFloor = false;
  142. mOptions.forceUpdateMaterials = false;
  143. mOptions.useDiffuseNames = false;
  144. mOptions.convertLeftHanded = false;
  145. mOptions.calcTangentSpace = false;
  146. mOptions.genUVCoords = false;
  147. mOptions.transformUVCoords = false;
  148. mOptions.flipUVCoords = true;
  149. mOptions.findInstances = false;
  150. mOptions.limitBoneWeights = false;
  151. mOptions.joinIdenticalVerts = true;
  152. mOptions.reverseWindingOrder = true;
  153. mOptions.invertNormals = false;
  154. mOptions.removeRedundantMats = true;
  155. mOptions.animTiming = ColladaUtils::ImportOptions::Seconds;
  156. mOptions.animFPS = 30;
  157. mOptions.formatScaleFactor = 1.0f;
  158. mShape = NULL;
  159. }
  160. TSShapeConstructor::~TSShapeConstructor()
  161. {
  162. }
  163. bool TSShapeConstructor::addSequenceFromField(void* obj, const char* index, const char* data)
  164. {
  165. TSShapeConstructor* pObj = static_cast<TSShapeConstructor*>(obj);
  166. if (data && data[0])
  167. pObj->mSequenceAssetIds.push_back(StringTable->insert(data));
  168. return false;
  169. }
  170. void TSShapeConstructor::initPersistFields()
  171. {
  172. docsURL;
  173. addGroup("Media");
  174. addField("baseShapeAsset", TypeShapeAssetId, Offset(mShapeAssetId, TSShapeConstructor),
  175. "Specifies the path to the DTS or DAE file to be operated on by this object.\n"
  176. "Since the TSShapeConstructor script must be in the same folder as the DTS or "
  177. "DAE file, it is recommended to use a relative path so that the shape and "
  178. "script files can be copied to another location without having to modify the "
  179. "path.");
  180. endGroup("Media");
  181. addGroup("Collada");
  182. addField("upAxis", TYPEID< domUpAxisType >(), Offset(mOptions.upAxis, TSShapeConstructor),
  183. "Override the <up_axis> element in the COLLADA (.dae) file. No effect for DTS files.\n"
  184. "Set to one of the following values:\n"
  185. "<dl><dt>X_AXIS</dt><dd>Positive X points up. Model will be rotated into Torque's coordinate system (Z up).</dd>"
  186. "<dt>Y_AXIS</dt><dd>Positive Y points up. Model will be rotated into Torque's coordinate system (Z up).</dd>"
  187. "<dt>Z_AXIS</dt><dd>Positive Z points up. No rotation will be applied to the model.</dd>"
  188. "<dt>DEFAULT</dt><dd>The default value. Use the value in the .dae file (defaults to Z_AXIS if the <up_axis> element is not present).</dd></dl>");
  189. addField("unit", TypeF32, Offset(mOptions.unit, TSShapeConstructor),
  190. "Override the <unit> element in the COLLADA (.dae) file. No effect for DTS files.\n"
  191. "COLLADA (.dae) files usually contain a <unit> element that indicates the "
  192. "'real world' units that the model is described in. It means you can work "
  193. "in sensible and meaningful units in your modeling app.<br>\n"
  194. "For example, if you were modeling a small object like a cup, it might make "
  195. "sense to work in inches (1 MAX unit = 1 inch), but if you were modeling a "
  196. "building, it might make more sense to work in feet (1 MAX unit = 1 foot). "
  197. "If you export both models to COLLADA, T3D will automatically scale them "
  198. "appropriately. 1 T3D unit = 1 meter, so the cup would be scaled down by 0.0254, "
  199. "and the building scaled down by 0.3048, given them both the correct scale "
  200. "relative to each other.<br>\n"
  201. "Omit the field or set to -1 to use the value in the .dae file (1.0 if the "
  202. "<unit> element is not present)");
  203. addField("lodType", TYPEID< ColladaUtils::ImportOptions::eLodType >(), Offset(mOptions.lodType, TSShapeConstructor),
  204. "Control how the COLLADA (.dae) importer interprets LOD in the model. No effect for DTS files.\n"
  205. "Set to one of the following values:\n"
  206. "<dl><dt>DetectDTS</dt><dd>The default value. Instructs the importer to search for a 'baseXXX->startXXX' node hierarchy at the root level. If found, the importer acts as if ''TrailingNumber'' was set. Otherwise, all geometry is imported at a single detail size.</dd>"
  207. "<dt>SingleSize</dt><dd>All geometry is imported at a fixed detail size. Numbers at the end of geometry node's are ignored.</dd>"
  208. "<dt>TrailingNumber</dt><dd>Numbers at the end of geometry node's name are interpreted as the detail size (similar to DTS exporting). Geometry instances with the same base name but different trailing number are grouped into the same object.</dd>"
  209. "<dt>DEFAULT</dt><dd>The default value. Use the value in the .dae file (defaults to Z_AXIS if the <up_axis> element is not present).</dd></dl>");
  210. addField("singleDetailSize", TypeS32, Offset(mOptions.singleDetailSize, TSShapeConstructor),
  211. "Sets the detail size when lodType is set to SingleSize. No effect otherwise, and no effect for DTS files.\n"
  212. "@see lodType");
  213. addField("matNamePrefix", TypeRealString, Offset(mOptions.matNamePrefix, TSShapeConstructor),
  214. "Prefix to apply to all material map names in the COLLADA (.dae) file. No effect for DTS files.\n"
  215. "This field is useful to avoid material name clashes for exporters that generate generic material "
  216. "names like \"texture0\" or \"material1\".");
  217. addField("alwaysImport", TypeRealString, Offset(mOptions.alwaysImport, TSShapeConstructor),
  218. "TAB separated patterns of nodes to import even if in neverImport list. No effect for DTS files.\n"
  219. "Torque allows unwanted nodes in COLLADA (.dae) files to to be ignored "
  220. "during import. This field contains a TAB separated list of patterns to "
  221. "match node names. Any node that matches one of the patterns in the list "
  222. "will <b>always</b> be imported, even if it also matches the neverImport list\n"
  223. "@see neverImport\n\n"
  224. "@tsexample\n"
  225. "singleton TSShapeConstructor(MyShapeDae)\n"
  226. "{\n"
  227. " baseShape = \"./myShape.dae\";\n"
  228. " alwaysImport = \"mount*\" TAB \"eye\";\n"
  229. " neverImport = \"*-PIVOT\";\n"
  230. "}\n"
  231. "@endtsexample");
  232. addField("neverImport", TypeRealString, Offset(mOptions.neverImport, TSShapeConstructor),
  233. "TAB separated patterns of nodes to ignore on loading. No effect for DTS files.\n"
  234. "Torque allows unwanted nodes in COLLADA (.dae) files to to be ignored "
  235. "during import. This field contains a TAB separated list of patterns to "
  236. "match node names. Any node that matches one of the patterns in the list will "
  237. "not be imported (unless it matches the alwaysImport list.\n"
  238. "@see alwaysImport");
  239. addField("alwaysImportMesh", TypeRealString, Offset(mOptions.alwaysImportMesh, TSShapeConstructor),
  240. "TAB separated patterns of meshes to import even if in neverImportMesh list. No effect for DTS files.\n"
  241. "Torque allows unwanted meshes in COLLADA (.dae) files to to be ignored "
  242. "during import. This field contains a TAB separated list of patterns to "
  243. "match mesh names. Any mesh that matches one of the patterns in the list "
  244. "will <b>always</b> be imported, even if it also matches the neverImportMesh list\n"
  245. "@see neverImportMesh\n\n"
  246. "@tsexample\n"
  247. "singleton TSShapeConstructor(MyShapeDae)\n"
  248. "{\n"
  249. " baseShape = \"./myShape.dae\";\n"
  250. " alwaysImportMesh = \"body*\" TAB \"armor\" TAB \"bounds\";\n"
  251. " neverImportMesh = \"*-dummy\";\n"
  252. "}\n"
  253. "@endtsexample");
  254. addField("neverImportMesh", TypeRealString, Offset(mOptions.neverImportMesh, TSShapeConstructor),
  255. "TAB separated patterns of meshes to ignore on loading. No effect for DTS files.\n"
  256. "Torque allows unwanted meshes in COLLADA (.dae) files to to be ignored "
  257. "during import. This field contains a TAB separated list of patterns to "
  258. "match mesh names. Any mesh that matches one of the patterns in the list will "
  259. "not be imported (unless it matches the alwaysImportMesh list.\n"
  260. "@see alwaysImportMesh");
  261. addField("neverImportMat", TypeRealString, Offset(mOptions.neverImportMat, TSShapeConstructor),
  262. "TAB separated patterns of materials to ignore on loading. No effect for DTS files.\n"
  263. "Torque allows unwanted materials in COLLADA (.dae) files to to be ignored "
  264. "during import. This field contains a TAB separated list of patterns to "
  265. "match material names. Any material that matches one of the patterns in the list will "
  266. "not be imported");
  267. addField("ignoreNodeScale", TypeBool, Offset(mOptions.ignoreNodeScale, TSShapeConstructor),
  268. "Ignore <scale> elements inside COLLADA <node>s. No effect for DTS files.\n"
  269. "This field is a workaround for certain exporters that generate bad node "
  270. "scaling, and is not usually required.");
  271. addField("adjustCenter", TypeBool, Offset(mOptions.adjustCenter, TSShapeConstructor),
  272. "Translate COLLADA model on import so the origin is at the center. No effect for DTS files.");
  273. addField("adjustFloor", TypeBool, Offset(mOptions.adjustFloor, TSShapeConstructor),
  274. "Translate COLLADA model on import so origin is at the (Z axis) bottom of the model. No effect for DTS files.\n"
  275. "This can be used along with adjustCenter to have the origin at the "
  276. "center of the bottom of the model.\n"
  277. "@see adjustCenter");
  278. addField("forceUpdateMaterials", TypeBool, Offset(mOptions.forceUpdateMaterials, TSShapeConstructor),
  279. "Forces update of the materials." TORQUE_SCRIPT_EXTENSION " file in the same folder as the COLLADA "
  280. "(.dae) file, even if Materials already exist. No effect for DTS files.\n"
  281. "Normally only Materials that are not already defined are written to materials." TORQUE_SCRIPT_EXTENSION ".");
  282. // Fields added for assimp options
  283. addField("convertLeftHanded", TypeBool, Offset(mOptions.convertLeftHanded, TSShapeConstructor),
  284. "Convert to left handed coordinate system.");
  285. addField("calcTangentSpace", TypeBool, Offset(mOptions.calcTangentSpace, TSShapeConstructor),
  286. "Calculate tangents and bitangents, if possible.");
  287. addField("genUVCoords", TypeBool, Offset(mOptions.genUVCoords, TSShapeConstructor),
  288. "Convert spherical, cylindrical, box and planar mapping to proper UVs.");
  289. addField("transformUVCoords", TypeBool, Offset(mOptions.transformUVCoords, TSShapeConstructor),
  290. "Preprocess UV transformations (scaling, translation ...).");
  291. addField("flipUVCoords", TypeBool, Offset(mOptions.flipUVCoords, TSShapeConstructor),
  292. "This step flips all UV coordinates along the y-axis and adjusts material settings and bitangents accordingly.\n"
  293. "Assimp uses TL(0,0):BR(1,1). T3D uses TL(0,1):BR(1,0). This will be needed for most textured models.");
  294. addField("findInstances", TypeBool, Offset(mOptions.findInstances, TSShapeConstructor),
  295. "Search for instanced meshes and remove them by references to one master.");
  296. addField("limitBoneWeights", TypeBool, Offset(mOptions.limitBoneWeights, TSShapeConstructor),
  297. "Limit bone weights to 4 per vertex.");
  298. addField("joinIdenticalVerts", TypeBool, Offset(mOptions.joinIdenticalVerts, TSShapeConstructor),
  299. "Identifies and joins identical vertex data sets within all imported meshes.");
  300. addField("reverseWindingOrder", TypeBool, Offset(mOptions.reverseWindingOrder, TSShapeConstructor),
  301. "This step adjusts the output face winding order to be clockwise. The default assimp face winding order is counter clockwise.");
  302. addField("invertNormals", TypeBool, Offset(mOptions.invertNormals, TSShapeConstructor),
  303. "Reverse the normal vector direction for all normals.");
  304. addField("removeRedundantMats", TypeBool, Offset(mOptions.removeRedundantMats, TSShapeConstructor),
  305. "Removes redundant materials.");
  306. addField("animTiming", TYPEID< ColladaUtils::ImportOptions::eAnimTimingType >(), Offset(mOptions.animTiming, TSShapeConstructor),
  307. "How to import timing data as frames, seconds or milliseconds.");
  308. addField("animFPS", TypeS32, Offset(mOptions.animFPS, TSShapeConstructor),
  309. "FPS value to use if timing is set in frames and the animations does not have an fps set.");
  310. endGroup("Collada");
  311. addGroup("Sequences");
  312. addProtectedField("sequence", TypeStringFilename, 0, &addSequenceFromField, &emptyStringProtectedGetFn,
  313. "Legacy method of adding sequences to a DTS or DAE shape after loading.\n\n"
  314. "@tsexample\n"
  315. "singleton TSShapeConstructor(MyShapeDae)\n"
  316. "{\n"
  317. " baseShape = \"./myShape.dae\";\n"
  318. " sequence = \"../anims/root.dae root\";\n"
  319. " sequence = \"../anims/walk.dae walk\";\n"
  320. " sequence = \"../anims/jump.dsq jump\";\n"
  321. "}\n"
  322. "@endtsexample");
  323. endGroup("Sequences");
  324. Parent::initPersistFields();
  325. }
  326. void TSShapeConstructor::consoleInit()
  327. {
  328. Parent::consoleInit();
  329. Con::addVariable("$pref::TSShapeConstructor::CapsuleShapePath", TypeRealString, &TSShapeConstructor::smCapsuleShapePath,
  330. "The file path to the capsule shape used by tsMeshFit.\n\n"
  331. "@ingroup MeshFit\n");
  332. Con::addVariable("$pref::TSShapeConstructor::CubeShapePath", TypeRealString, &TSShapeConstructor::smCubeShapePath,
  333. "The file path to the cube shape used by tsMeshFit.\n\n"
  334. "@ingroup MeshFit\n");
  335. Con::addVariable("$pref::TSShapeConstructor::SphereShapePath", TypeRealString, &TSShapeConstructor::smSphereShapePath,
  336. "The file path to the sphere shape used by tsMeshFit.\n\n"
  337. "@ingroup MeshFit\n");
  338. }
  339. TSShapeConstructor* TSShapeConstructor::findShapeConstructorByAssetId(StringTableEntry shapeAssetId)
  340. {
  341. SimGroup* group;
  342. if (Sim::findObject("TSShapeConstructorGroup", group))
  343. {
  344. // Find the TSShapeConstructor object for the given shape file
  345. for (S32 i = 0; i < group->size(); i++)
  346. {
  347. TSShapeConstructor* tss = dynamic_cast<TSShapeConstructor*>(group->at(i));
  348. StringTableEntry targetAssetId = tss->getShapeAssetId();
  349. if(targetAssetId == shapeAssetId)
  350. return tss;
  351. }
  352. }
  353. return NULL;
  354. }
  355. TSShapeConstructor* TSShapeConstructor::findShapeConstructorByFilename(const FileName& path)
  356. {
  357. SimGroup* group;
  358. if (Sim::findObject("TSShapeConstructorGroup", group))
  359. {
  360. // Find the TSShapeConstructor object for the given shape file
  361. for (S32 i = 0; i < group->size(); i++)
  362. {
  363. TSShapeConstructor* tss = dynamic_cast<TSShapeConstructor*>(group->at(i));
  364. FileName shapePath = (FileName)(tss->getShapePath());
  365. char buf[1024];
  366. FileName fullShapePath = String(Platform::makeFullPathName(shapePath, buf, sizeof(buf)));
  367. if (shapePath.equal(path, String::NoCase) || fullShapePath.equal(path, String::NoCase))
  368. return tss;
  369. }
  370. }
  371. return NULL;
  372. }
  373. //-----------------------------------------------------------------------------
  374. bool TSShapeConstructor::onAdd()
  375. {
  376. if (!Parent::onAdd())
  377. return false;
  378. // Prevent multiple objects pointing at the same shape file
  379. TSShapeConstructor* tss = findShapeConstructorByAssetId(getShapeAssetId());
  380. if (tss)
  381. {
  382. Con::errorf("TSShapeConstructor::onAdd failed: %s is already referenced by "
  383. "another TSShapeConstructor object (%s - %d)", getShapeAssetId(),
  384. tss->getName(), tss->getId());
  385. return false;
  386. }
  387. // Add to the TSShapeConstructor group (for lookups)
  388. SimGroup* group;
  389. if (!Sim::findObject("TSShapeConstructorGroup", group))
  390. {
  391. group = new SimGroup();
  392. if (!group->registerObject("TSShapeConstructorGroup"))
  393. {
  394. SAFE_DELETE(group);
  395. Con::errorf("TSShapeConstructor::onAdd failed: Could not register "
  396. "TSShapeConstructorGroup");
  397. return false;
  398. }
  399. Sim::getRootGroup()->addObject(group);
  400. }
  401. group->addObject(this);
  402. // This is only here for backwards compatibility!
  403. //
  404. // If we have no sequences, it may be using the older sequence# syntax.
  405. // Check for dynamic fields of that pattern and add them into the sequence vector.
  406. /*if (mSequenceAssetIds.empty())
  407. {
  408. for ( U32 idx = 0; idx < MaxLegacySequences; idx++ )
  409. {
  410. String field = String::ToString( "sequence%d", idx );
  411. const char *data = getDataField( StringTable->insert(field.c_str()), NULL );
  412. // Break at first field not used
  413. if ( !data || !data[0] )
  414. break;
  415. // By pushing the field thru Con::setData for TypeStringFilename
  416. // we get the default filename expansion. If we didn't do this
  417. // then we would have unexpanded ~/ in the file paths.
  418. String expanded;
  419. Con::setData( TypeStringFilename, &expanded, 0, 1, &data );
  420. addSequenceFromField( this, NULL, expanded.c_str() );
  421. }
  422. }*/
  423. // If an instance of this shape has already been loaded, call onLoad now
  424. mShapeAsset = mShapeAssetId;
  425. if (mShapeAsset.notNull())
  426. {
  427. Resource<TSShape> shape = mShapeAsset->getShapeResource();
  428. if (shape)
  429. _onLoad(shape);
  430. }
  431. if (mShape && mShape->needsReinit())
  432. {
  433. mShape->init();
  434. }
  435. return true;
  436. }
  437. //-----------------------------------------------------------------------------
  438. void TSShapeConstructor::_onLoad(TSShape* shape)
  439. {
  440. // Check if we should unload first
  441. if (mShape)
  442. _onUnload();
  443. #ifdef DEBUG_SPEW
  444. Con::printf("[TSShapeConstructor] attaching to shape '%s'", getShapePath());
  445. #endif
  446. mShape = shape;
  447. mChangeSet.clear();
  448. mLoadingShape = true;
  449. // Add sequences defined using field syntax
  450. for (S32 i = 0; i < mSequenceAssetIds.size(); i++)
  451. {
  452. if (mSequenceAssetIds[i] == StringTable->EmptyString())
  453. continue;
  454. AssetPtr<ShapeAnimationAsset> sequenceAsset = mSequenceAssetIds[i];
  455. if (sequenceAsset.isNull())
  456. continue;
  457. // Split the sequence path from the target sequence name
  458. String destName;
  459. String srcPath(sequenceAsset->getAnimationPath());
  460. SplitSequencePathAndName(srcPath, destName);
  461. addSequence(srcPath, destName);
  462. }
  463. // Call script function
  464. onLoad_callback();
  465. mLoadingShape = false;
  466. }
  467. //-----------------------------------------------------------------------------
  468. void TSShapeConstructor::_onUnload()
  469. {
  470. #ifdef DEBUG_SPEW
  471. Con::printf("[TSShapeConstructor] detaching from '%s'", getShapePath());
  472. #endif
  473. onUnload_callback();
  474. mShape = NULL;
  475. }
  476. //-----------------------------------------------------------------------------
  477. void TSShapeConstructor::setShapeAssetId(StringTableEntry assetId)
  478. {
  479. mShapeAssetId = assetId;
  480. mShapeAsset = mShapeAssetId;
  481. if (mShapeAsset.notNull())
  482. {
  483. Resource<TSShape> shape = mShapeAsset->getShapeResource();
  484. if (shape)
  485. _onLoad(shape);
  486. }
  487. if (mShape && mShape->needsReinit())
  488. {
  489. mShape->init();
  490. }
  491. }
  492. //-----------------------------------------------------------------------------
  493. // Storage
  494. bool TSShapeConstructor::writeField(StringTableEntry fieldname, const char* value)
  495. {
  496. // Ignore the sequence fields (these are written as 'addSequence' commands instead)
  497. if (dStrnicmp(fieldname, "sequence", 8) == 0)
  498. return false;
  499. else if (dStrnicmp(fieldname, "baseShape", 9) == 0)
  500. {
  501. // Small hack to only write the base filename (no path) since the
  502. // TSShapeConstructor script must be in the same folder as the model, and
  503. // then we can easily copy both around without having to change the field
  504. const char* filename = dStrrchr(value, '/');
  505. if (filename > value)
  506. {
  507. S32 len = dStrlen(filename);
  508. dMemmove((void*)(value + 1), filename, len);
  509. ((char*)value)[0] = '.';
  510. ((char*)value)[len + 1] = '\0';
  511. }
  512. return true;
  513. }
  514. return Parent::writeField(fieldname, value);
  515. }
  516. //-----------------------------------------------------------------------------
  517. // Console utility methods
  518. // These macros take care of doing node, sequence and object lookups, including
  519. // printing error messages to the console if the element cannot be found.
  520. // Check that the given index is valid (0 - max-1). If not, generate an
  521. // error and return.
  522. #define CHECK_INDEX_IN_RANGE(func, index, maxIndex, ret) \
  523. if ( ( index < 0 ) || ( index >= maxIndex ) ) \
  524. { \
  525. Con::errorf( "TSShapeConstructor::" #func ": index out of " \
  526. "range (0-%d)", maxIndex-1); \
  527. return ret; \
  528. }
  529. // Do a node lookup and allow the root node name ("")
  530. #define GET_NODE_INDEX_ALLOW_ROOT(func, var, name, ret) \
  531. S32 var##Index = -1; \
  532. if (name[0]) \
  533. { \
  534. var##Index = mShape->findNode(name); \
  535. if (var##Index < 0) \
  536. { \
  537. Con::errorf( "TSShapeConstructor::" #func ": Could not " \
  538. "find node '%s'", name); \
  539. return ret; \
  540. } \
  541. } \
  542. TSShape::Node* var = var##Index < 0 ? NULL : &(mShape->nodes[var##Index]); \
  543. TORQUE_UNUSED(var##Index); \
  544. TORQUE_UNUSED(var)
  545. // Do a node lookup, root node ("") is not allowed
  546. #define GET_NODE_INDEX_NO_ROOT(func, var, name, ret) \
  547. S32 var##Index = mShape->findNode(name); \
  548. if (var##Index < 0) \
  549. { \
  550. Con::errorf( "TSShapeConstructor::" #func ": Could not find " \
  551. "node '%s'", name); \
  552. return ret; \
  553. } \
  554. TSShape::Node* var = &(mShape->nodes[var##Index]); \
  555. TORQUE_UNUSED(var##Index); \
  556. TORQUE_UNUSED(var)
  557. // Do an object lookup
  558. #define GET_OBJECT(func, var, name, ret) \
  559. S32 var##Index = mShape->findObject(name); \
  560. if (var##Index < 0) \
  561. { \
  562. Con::errorf( "TSShapeConstructor::" #func ": Could not find " \
  563. "object '%s'", name); \
  564. return ret; \
  565. } \
  566. TSShape::Object* var = &(mShape->objects[var##Index]); \
  567. TORQUE_UNUSED(var##Index); \
  568. TORQUE_UNUSED(var)
  569. // Do a mesh lookup
  570. #define GET_MESH(func, var, name, ret) \
  571. TSMesh* var = mShape->findMesh(name); \
  572. if (!var) \
  573. { \
  574. Con::errorf( "TSShapeConstructor::" #func ": Could not find " \
  575. "mesh '%s'", name); \
  576. return ret; \
  577. }
  578. // Do a sequence lookup
  579. #define GET_SEQUENCE(func, var, name, ret) \
  580. S32 var##Index = mShape->findSequence(name); \
  581. if (var##Index < 0) \
  582. { \
  583. Con::errorf( "TSShapeConstructor::" #func ": Could not find " \
  584. "sequence named '%s'", name); \
  585. return ret; \
  586. } \
  587. TSShape::Sequence* var = &(mShape->sequences[var##Index]); \
  588. TORQUE_UNUSED(var##Index); \
  589. TORQUE_UNUSED(var);
  590. //-----------------------------------------------------------------------------
  591. // DUMP
  592. DefineTSShapeConstructorMethod(dumpShape, void, (const char* filename), (""),
  593. (filename), ,
  594. "Dump the shape hierarchy to the console or to a file. Useful for reviewing "
  595. "the result of a series of construction commands.\n"
  596. "@param filename Destination filename. If not specified, dump to console.\n\n"
  597. "@tsexample\n"
  598. "%this.dumpShape(); // dump to console\n"
  599. "%this.dumpShape( \"./dump.txt\" ); // dump to file\n"
  600. "@endtsexample\n")
  601. {
  602. TSShapeInstance* tsi = new TSShapeInstance(mShape, false);
  603. if (dStrEqual(filename, ""))
  604. {
  605. // Dump the constructed shape to a memory stream
  606. MemStream* dumpStream = new MemStream(8192);
  607. tsi->dump(*dumpStream);
  608. // Write stream to the console
  609. U32 end = dumpStream->getPosition();
  610. dumpStream->setPosition(0);
  611. while (dumpStream->getPosition() < end)
  612. {
  613. char line[1024];
  614. dumpStream->readLine((U8*)line, sizeof(line));
  615. Con::printf(line);
  616. }
  617. delete dumpStream;
  618. }
  619. else
  620. {
  621. // Dump constructed shape to file
  622. char filenameBuf[1024];
  623. Con::expandScriptFilename(filenameBuf, sizeof(filenameBuf), filename);
  624. FileStream* dumpStream = new FileStream;
  625. if (dumpStream->open(filenameBuf, Torque::FS::File::Write))
  626. {
  627. tsi->dump(*dumpStream);
  628. dumpStream->close();
  629. }
  630. else
  631. Con::errorf("dumpShape failed: Could not open file '%s' for writing", filenameBuf);
  632. delete dumpStream;
  633. }
  634. delete tsi;
  635. }}
  636. DefineTSShapeConstructorMethod(saveShape, void, (const char* filename), ,
  637. (filename), ,
  638. "Save the shape (with all current changes) to a new DTS file.\n"
  639. "@param filename Destination filename.\n\n"
  640. "@tsexample\n"
  641. "%this.saveShape( \"./myShape.dts\" );\n"
  642. "@endtsexample\n")
  643. {
  644. char filenameBuf[1024];
  645. Con::expandScriptFilename(filenameBuf, sizeof(filenameBuf), filename);
  646. FileStream* dtsStream = new FileStream;
  647. if (dtsStream->open(filenameBuf, Torque::FS::File::Write))
  648. {
  649. mShape->write(dtsStream);
  650. dtsStream->close();
  651. }
  652. else
  653. {
  654. Con::errorf("saveShape failed: Could not open '%s' for writing", filenameBuf);
  655. }
  656. delete dtsStream;
  657. }}
  658. DefineTSShapeConstructorMethod(writeChangeSet, void, (), ,
  659. (), ,
  660. "Write the current change set to a TSShapeConstructor script file. The "
  661. "name of the script file is the same as the model, but with ." TORQUE_SCRIPT_EXTENSION " extension. "
  662. "eg. myShape." TORQUE_SCRIPT_EXTENSION " for myShape.dts or myShape.dae.\n")
  663. {
  664. Torque::Path scriptPath(getShapePath());
  665. scriptPath.setExtension(TORQUE_SCRIPT_EXTENSION);
  666. // Read current file contents
  667. FileObject f;
  668. f.readMemory(scriptPath.getFullPath());
  669. // Write new file
  670. FileStream* stream;
  671. if ((stream = FileStream::createAndOpen(scriptPath.getFullPath(), Torque::FS::File::Write)) == NULL)
  672. {
  673. Con::errorf("Failed to write TSShapeConstructor change set to %s", scriptPath.getFullPath().c_str());
  674. return;
  675. }
  676. // Write existing file contents up to the start of the onLoad function
  677. String beginMessage(avar("function %s::onLoad(%%this)", getName()));
  678. String endMessage("}");
  679. while (!f.isEOF())
  680. {
  681. const char* buffer = (const char*)f.readLine();
  682. if (!String::compare(buffer, beginMessage.c_str()))
  683. break;
  684. stream->writeText(buffer);
  685. stream->writeText("\r\n");
  686. }
  687. // Write the new contents
  688. if (f.isEOF())
  689. stream->writeText("\r\n");
  690. stream->writeText(beginMessage);
  691. stream->writeText("\r\n{\r\n");
  692. mChangeSet.write(mShape, *stream, scriptPath.getPath());
  693. stream->writeText(endMessage);
  694. stream->writeText("\r\n");
  695. // Now skip the contents of the function
  696. while (!f.isEOF())
  697. {
  698. const char* buffer = (const char*)f.readLine();
  699. if (!String::compare(buffer, endMessage.c_str()))
  700. break;
  701. }
  702. // Write the remainder of the existing file contents
  703. while (!f.isEOF())
  704. {
  705. const char* buffer = (const char*)f.readLine();
  706. stream->writeText(buffer);
  707. stream->writeText("\r\n");
  708. }
  709. delete stream;
  710. }}
  711. DefineTSShapeConstructorMethod(notifyShapeChanged, void, (), ,
  712. (), ,
  713. "Notify game objects that this shape file has changed, allowing them to update "
  714. "internal data if needed.")
  715. {
  716. ResourceManager::get().getChangedSignal().trigger(getShapePath());
  717. }}
  718. //-----------------------------------------------------------------------------
  719. // NODES
  720. DefineTSShapeConstructorMethod(getNodeCount, S32, (), ,
  721. (), 0,
  722. "Get the total number of nodes in the shape.\n"
  723. "@return the number of nodes in the shape.\n\n"
  724. "@tsexample\n"
  725. "%count = %this.getNodeCount();\n"
  726. "@endtsexample\n")
  727. {
  728. return mShape->nodes.size();
  729. }}
  730. DefineTSShapeConstructorMethod(getNodeIndex, S32, (const char* name), ,
  731. (name), -1,
  732. "Get the index of the node.\n"
  733. "@param name name of the node to lookup.\n"
  734. "@return the index of the named node, or -1 if no such node exists.\n\n"
  735. "@tsexample\n"
  736. "// get the index of Bip01 Pelvis node in the shape\n"
  737. "%index = %this.getNodeIndex( \"Bip01 Pelvis\" );\n"
  738. "@endtsexample\n")
  739. {
  740. return mShape->findNode(name);
  741. }}
  742. DefineTSShapeConstructorMethod(getNodeName, const char*, (S32 index), ,
  743. (index), "",
  744. "Get the name of the indexed node.\n"
  745. "@param index index of the node to lookup (valid range is 0 - getNodeCount()-1).\n"
  746. "@return the name of the indexed node, or \"\" if no such node exists.\n\n"
  747. "@tsexample\n"
  748. "// print the names of all the nodes in the shape\n"
  749. "%count = %this.getNodeCount();\n"
  750. "for (%i = 0; %i < %count; %i++)\n"
  751. " echo(%i SPC %this.getNodeName(%i));\n"
  752. "@endtsexample\n")
  753. {
  754. CHECK_INDEX_IN_RANGE(getNodeName, index, mShape->nodes.size(), "");
  755. return mShape->getName(mShape->nodes[index].nameIndex);
  756. }}
  757. DefineTSShapeConstructorMethod(getNodeParentName, const char*, (const char* name), ,
  758. (name), "",
  759. "Get the name of the node's parent. If the node has no parent (ie. it is at "
  760. "the root level), return an empty string.\n"
  761. "@param name name of the node to query.\n"
  762. "@return the name of the node's parent, or \"\" if the node is at the root level\n\n"
  763. "@tsexample\n"
  764. "echo( \"Bip01 Pelvis parent = \" @ %this.getNodeParentName( \"Bip01 Pelvis \") );\n"
  765. "@endtsexample\n")
  766. {
  767. GET_NODE_INDEX_NO_ROOT(getNodeParentName, node, name, "");
  768. if (node->parentIndex < 0)
  769. return "";
  770. else
  771. return mShape->getName(mShape->nodes[node->parentIndex].nameIndex);
  772. }}
  773. DefineTSShapeConstructorMethod(setNodeParent, bool, (const char* name, const char* parentName), ,
  774. (name, parentName), false,
  775. "Set the parent of a node.\n"
  776. "@param name name of the node to modify\n"
  777. "@param parentName name of the parent node to set (use \"\" to move the node to the root level)\n"
  778. "@return true if successful, false if failed\n\n"
  779. "@tsexample\n"
  780. "%this.setNodeParent( \"Bip01 Pelvis\", \"start01\" );\n"
  781. "@endtsexample\n")
  782. {
  783. GET_NODE_INDEX_NO_ROOT(setNodeParent, node, name, false);
  784. GET_NODE_INDEX_ALLOW_ROOT(setNodeParent, parent, parentName, false);
  785. node->parentIndex = parentIndex;
  786. ADD_TO_CHANGE_SET();
  787. return true;
  788. }}
  789. DefineTSShapeConstructorMethod(getNodeChildCount, S32, (const char* name), ,
  790. (name), 0,
  791. "Get the number of children of this node.\n"
  792. "@param name name of the node to query.\n"
  793. "@return the number of child nodes.\n\n"
  794. "@tsexample\n"
  795. "%count = %this.getNodeChildCount( \"Bip01 Pelvis\" );\n"
  796. "@endtsexample\n")
  797. {
  798. GET_NODE_INDEX_ALLOW_ROOT(getNodeChildCount, node, name, 0);
  799. Vector<S32> nodeChildren;
  800. mShape->getNodeChildren(nodeIndex, nodeChildren);
  801. return nodeChildren.size();
  802. }}
  803. DefineTSShapeConstructorMethod(getNodeChildName, const char*, (const char* name, S32 index), ,
  804. (name, index), "",
  805. "Get the name of the indexed child node.\n"
  806. "@param name name of the parent node to query.\n"
  807. "@param index index of the child node (valid range is 0 - getNodeChildName()-1).\n"
  808. "@return the name of the indexed child node.\n\n"
  809. "@tsexample\n"
  810. "function dumpNode( %shape, %name, %indent )\n"
  811. "{\n"
  812. " echo( %indent @ %name );\n"
  813. " %count = %shape.getNodeChildCount( %name );\n"
  814. " for ( %i = 0; %i < %count; %i++ )\n"
  815. " dumpNode( %shape, %shape.getNodeChildName( %name, %i ), %indent @ \" \" );\n"
  816. "}\n\n"
  817. "function dumpShape( %shape )\n"
  818. "{\n"
  819. " // recursively dump node hierarchy\n"
  820. " %count = %shape.getNodeCount();\n"
  821. " for ( %i = 0; %i < %count; %i++ )\n"
  822. " {\n"
  823. " // dump top level nodes\n"
  824. " %name = %shape.getNodeName( %i );\n"
  825. " if ( %shape.getNodeParentName( %name ) $= "" )\n"
  826. " dumpNode( %shape, %name, \"\" );\n"
  827. " }\n"
  828. "}\n"
  829. "@endtsexample\n")
  830. {
  831. GET_NODE_INDEX_ALLOW_ROOT(getNodeChildName, node, name, "");
  832. Vector<S32> nodeChildren;
  833. mShape->getNodeChildren(nodeIndex, nodeChildren);
  834. CHECK_INDEX_IN_RANGE(getNodeChildName, index, nodeChildren.size(), "");
  835. return mShape->getName(mShape->nodes[nodeChildren[index]].nameIndex);
  836. }}
  837. DefineTSShapeConstructorMethod(getNodeObjectCount, S32, (const char* name), ,
  838. (name), 0,
  839. "Get the number of geometry objects attached to this node.\n"
  840. "@param name name of the node to query.\n"
  841. "@return the number of attached objects.\n\n"
  842. "@tsexample\n"
  843. "%count = %this.getNodeObjectCount( \"Bip01 Head\" );\n"
  844. "@endtsexample\n")
  845. {
  846. GET_NODE_INDEX_ALLOW_ROOT(getNodeObjectCount, node, name, 0);
  847. Vector<S32> nodeObjects;
  848. mShape->getNodeObjects(nodeIndex, nodeObjects);
  849. return nodeObjects.size();
  850. }}
  851. DefineTSShapeConstructorMethod(getNodeObjectName, const char*, (const char* name, S32 index), ,
  852. (name, index), "",
  853. "Get the name of the indexed object.\n"
  854. "@param name name of the node to query.\n"
  855. "@param index index of the object (valid range is 0 - getNodeObjectCount()-1).\n"
  856. "@return the name of the indexed object.\n\n"
  857. "@tsexample\n"
  858. "// print the names of all objects attached to the node\n"
  859. "%count = %this.getNodeObjectCount( \"Bip01 Head\" );\n"
  860. "for ( %i = 0; %i < %count; %i++ )\n"
  861. " echo( %this.getNodeObjectName( \"Bip01 Head\", %i ) );\n"
  862. "@endtsexample\n")
  863. {
  864. GET_NODE_INDEX_ALLOW_ROOT(getNodeObjectName, node, name, "");
  865. Vector<S32> nodeObjects;
  866. mShape->getNodeObjects(nodeIndex, nodeObjects);
  867. CHECK_INDEX_IN_RANGE(getNodeObjectName, index, nodeObjects.size(), "");
  868. return mShape->getName(mShape->objects[nodeObjects[index]].nameIndex);
  869. }}
  870. DefineTSShapeConstructorMethod(getNodeTransform, TransformF, (const char* name, bool isWorld), (false),
  871. (name, isWorld), TransformF::Identity,
  872. "Get the base (ie. not animated) transform of a node.\n"
  873. "@param name name of the node to query.\n"
  874. "@param isWorld true to get the global transform, false (or omitted) to get "
  875. "the local-to-parent transform.\n"
  876. "@return the node transform in the form \"pos.x pos.y pos.z rot.x rot.y rot.z rot.angle\".\n\n"
  877. "@tsexample\n"
  878. "%ret = %this.getNodeTransform( \"mount0\" );\n"
  879. "%this.setNodeTransform( \"mount4\", %ret );\n"
  880. "@endtsexample\n")
  881. {
  882. GET_NODE_INDEX_NO_ROOT(getNodeTransform, node, name, TransformF::Identity);
  883. // Get the node transform
  884. Point3F pos;
  885. AngAxisF aa;
  886. if (isWorld)
  887. {
  888. // World transform
  889. MatrixF mat;
  890. mShape->getNodeWorldTransform(nodeIndex, &mat);
  891. pos = mat.getPosition();
  892. aa.set(mat);
  893. }
  894. else
  895. {
  896. // Local transform
  897. pos = mShape->defaultTranslations[nodeIndex];
  898. const Quat16& q16 = mShape->defaultRotations[nodeIndex];
  899. aa.set(q16.getQuatF());
  900. }
  901. return TransformF(pos, aa);
  902. }}
  903. DefineTSShapeConstructorMethod(setNodeTransform, bool, (const char* name, TransformF txfm, bool isWorld), (false),
  904. (name, txfm, isWorld), false,
  905. "Set the base transform of a node. That is, the transform of the node when "
  906. "in the root (not-animated) pose.\n"
  907. "@param name name of the node to modify\n"
  908. "@param txfm transform string of the form: \"pos.x pos.y pos.z rot.x rot.y rot.z rot.angle\"\n"
  909. "@param isworld (optional) flag to set the local-to-parent or the global "
  910. "transform. If false, or not specified, the position and orientation are "
  911. "treated as relative to the node's parent.\n"
  912. "@return true if successful, false otherwise\n\n"
  913. "@tsexample\n"
  914. "%this.setNodeTransform( \"mount0\", \"0 0 1 0 0 1 0\" );\n"
  915. "%this.setNodeTransform( \"mount0\", \"0 0 0 0 0 1 1.57\" );\n"
  916. "%this.setNodeTransform( \"mount0\", \"1 0 0 0 0 1 0\", true );\n"
  917. "@endtsexample\n")
  918. {
  919. GET_NODE_INDEX_NO_ROOT(setNodeTransform, node, name, false);
  920. Point3F pos(txfm.getPosition());
  921. QuatF rot(txfm.getOrientation());
  922. if (isWorld)
  923. {
  924. // World transform
  925. // Get the node's parent (if any)
  926. if (node->parentIndex != -1)
  927. {
  928. MatrixF mat;
  929. mShape->getNodeWorldTransform(node->parentIndex, &mat);
  930. // Pre-multiply by inverse of parent's world transform to get
  931. // local node transform
  932. mat.inverse();
  933. mat.mul(txfm.getMatrix());
  934. rot.set(mat);
  935. pos = mat.getPosition();
  936. }
  937. }
  938. if (!mShape->setNodeTransform(name, pos, rot))
  939. return false;
  940. ADD_TO_CHANGE_SET();
  941. return true;
  942. }}
  943. DefineTSShapeConstructorMethod(renameNode, bool, (const char* oldName, const char* newName), ,
  944. (oldName, newName), false,
  945. "Rename a node.\n"
  946. "@note Note that node names must be unique, so this command will fail if "
  947. "there is already a node with the desired name\n"
  948. "@param oldName current name of the node\n"
  949. "@param newName new name of the node\n"
  950. "@return true if successful, false otherwise\n\n"
  951. "@tsexample\n"
  952. "%this.renameNode( \"Bip01 L Hand\", \"mount5\" );\n"
  953. "@endtsexample\n")
  954. {
  955. GET_NODE_INDEX_NO_ROOT(renameNode, node, oldName, false);
  956. if (!mShape->renameNode(oldName, newName))
  957. return false;
  958. ADD_TO_CHANGE_SET();
  959. return true;
  960. }}
  961. DefineTSShapeConstructorMethod(addNode, bool, (const char* name, const char* parentName, TransformF txfm, bool isWorld), (TransformF::Identity, false),
  962. (name, parentName, txfm, isWorld), false,
  963. "Add a new node.\n"
  964. "@param name name for the new node (must not already exist)\n"
  965. "@param parentName name of an existing node to be the parent of the new node. "
  966. "If empty (\"\"), the new node will be at the root level of the node hierarchy.\n"
  967. "@param txfm (optional) transform string of the form: \"pos.x pos.y pos.z rot.x rot.y rot.z rot.angle\"\n"
  968. "@param isworld (optional) flag to set the local-to-parent or the global "
  969. "transform. If false, or not specified, the position and orientation are "
  970. "treated as relative to the node's parent.\n"
  971. "@return true if successful, false otherwise\n\n"
  972. "@tsexample\n"
  973. "%this.addNode( \"Nose\", \"Bip01 Head\", \"0 2 2 0 0 1 0\" );\n"
  974. "%this.addNode( \"myRoot\", \"\", \"0 0 4 0 0 1 1.57\" );\n"
  975. "%this.addNode( \"Nodes\", \"Bip01 Head\", \"0 2 0 0 0 1 0\", true );\n"
  976. "@endtsexample\n")
  977. {
  978. Point3F pos(txfm.getPosition());
  979. QuatF rot(txfm.getOrientation());
  980. if (isWorld)
  981. {
  982. // World transform
  983. // Get the node's parent (if any)
  984. S32 parentIndex = mShape->findNode(parentName);
  985. if (parentIndex != -1)
  986. {
  987. MatrixF mat;
  988. mShape->getNodeWorldTransform(parentIndex, &mat);
  989. // Pre-multiply by inverse of parent's world transform to get
  990. // local node transform
  991. mat.inverse();
  992. mat.mul(txfm.getMatrix());
  993. rot.set(mat);
  994. pos = mat.getPosition();
  995. }
  996. }
  997. if (!mShape->addNode(name, parentName, pos, rot))
  998. return false;
  999. ADD_TO_CHANGE_SET();
  1000. return true;
  1001. }}
  1002. DefineTSShapeConstructorMethod(removeNode, bool, (const char* name), ,
  1003. (name), false,
  1004. "Remove a node from the shape.\n"
  1005. "The named node is removed from the shape, including from any sequences that "
  1006. "use the node. Child nodes and objects attached to the node are re-assigned "
  1007. "to the node's parent.\n"
  1008. "@param name name of the node to remove.\n"
  1009. "@return true if successful, false otherwise.\n\n"
  1010. "@tsexample\n"
  1011. "%this.removeNode( \"Nose\" );\n"
  1012. "@endtsexample\n")
  1013. {
  1014. GET_NODE_INDEX_NO_ROOT(removeNode, node, name, false);
  1015. if (!mShape->removeNode(name))
  1016. return false;
  1017. ADD_TO_CHANGE_SET();
  1018. return true;
  1019. }}
  1020. //-----------------------------------------------------------------------------
  1021. // MATERIALS
  1022. DefineTSShapeConstructorMethod(getTargetCount, S32, (), , (), 0,
  1023. "Get the number of materials in the shape.\n"
  1024. "@return the number of materials in the shape.\n\n"
  1025. "@tsexample\n"
  1026. "%count = %this.getTargetCount();\n"
  1027. "@endtsexample\n")
  1028. {
  1029. return mShape->getTargetCount();
  1030. }}
  1031. DefineTSShapeConstructorMethod(getTargetName, const char*, (S32 index), ,
  1032. (index), "",
  1033. "Get the name of the indexed shape material.\n"
  1034. "@param index index of the material to get (valid range is 0 - getTargetCount()-1).\n"
  1035. "@return the name of the indexed material.\n\n"
  1036. "@tsexample\n"
  1037. "%count = %this.getTargetCount();\n"
  1038. "for ( %i = 0; %i < %count; %i++ )\n"
  1039. " echo( \"Target \" @ %i @ \": \" @ %this.getTargetName( %i ) );\n"
  1040. "@endtsexample\n")
  1041. {
  1042. return mShape->getTargetName(index);
  1043. }}
  1044. //-----------------------------------------------------------------------------
  1045. // OBJECTS
  1046. DefineTSShapeConstructorMethod(getObjectCount, S32, (), , (), 0,
  1047. "Get the total number of objects in the shape.\n"
  1048. "@return the number of objects in the shape.\n\n"
  1049. "@tsexample\n"
  1050. "%count = %this.getObjectCount();\n"
  1051. "@endtsexample\n")
  1052. {
  1053. return mShape->objects.size();
  1054. }}
  1055. DefineTSShapeConstructorMethod(getObjectName, const char*, (S32 index), ,
  1056. (index), "",
  1057. "Get the name of the indexed object.\n"
  1058. "@param index index of the object to get (valid range is 0 - getObjectCount()-1).\n"
  1059. "@return the name of the indexed object.\n\n"
  1060. "@tsexample\n"
  1061. "// print the names of all objects in the shape\n"
  1062. "%count = %this.getObjectCount();\n"
  1063. "for ( %i = 0; %i < %count; %i++ )\n"
  1064. " echo( %i SPC %this.getObjectName( %i ) );\n"
  1065. "@endtsexample\n")
  1066. {
  1067. CHECK_INDEX_IN_RANGE(getObjectName, index, mShape->objects.size(), "");
  1068. return mShape->getName(mShape->objects[index].nameIndex);
  1069. }}
  1070. DefineTSShapeConstructorMethod(getObjectIndex, S32, (const char* name), ,
  1071. (name), -1,
  1072. "Get the index of the first object with the given name.\n"
  1073. "@param name name of the object to get.\n"
  1074. "@return the index of the named object.\n\n"
  1075. "@tsexample\n"
  1076. "%index = %this.getObjectIndex( \"Head\" );\n"
  1077. "@endtsexample\n")
  1078. {
  1079. return mShape->findObject(name);
  1080. }}
  1081. DefineTSShapeConstructorMethod(getObjectNode, const char*, (const char* name), ,
  1082. (name), "",
  1083. "Get the name of the node this object is attached to.\n"
  1084. "@param name name of the object to get.\n"
  1085. "@return the name of the attached node, or an empty string if this "
  1086. "object is not attached to a node (usually the case for skinned meshes).\n\n"
  1087. "@tsexample\n"
  1088. "echo( \"Hand is attached to \" @ %this.getObjectNode( \"Hand\" ) );\n"
  1089. "@endtsexample\n")
  1090. {
  1091. GET_OBJECT(getObjectNode, obj, name, 0);
  1092. if (obj->nodeIndex < 0)
  1093. return "";
  1094. else
  1095. return mShape->getName(mShape->nodes[obj->nodeIndex].nameIndex);
  1096. }}
  1097. DefineTSShapeConstructorMethod(setObjectNode, bool, (const char* objName, const char* nodeName), ,
  1098. (objName, nodeName), false,
  1099. "Set the node an object is attached to.\n"
  1100. "When the shape is rendered, the object geometry is rendered at the node's "
  1101. "current transform.\n"
  1102. "@param objName name of the object to modify\n"
  1103. "@param nodeName name of the node to attach the object to\n"
  1104. "@return true if successful, false otherwise\n\n"
  1105. "@tsexample\n"
  1106. "%this.setObjectNode( \"Hand\", \"Bip01 LeftHand\" );\n"
  1107. "@endtsexample\n")
  1108. {
  1109. if (!mShape->setObjectNode(objName, nodeName))
  1110. return false;
  1111. ADD_TO_CHANGE_SET();
  1112. return true;
  1113. }}
  1114. DefineTSShapeConstructorMethod(renameObject, bool, (const char* oldName, const char* newName), ,
  1115. (oldName, newName), false,
  1116. "Rename an object.\n"
  1117. "@note Note that object names must be unique, so this command will fail if "
  1118. "there is already an object with the desired name\n"
  1119. "@param oldName current name of the object\n"
  1120. "@param newName new name of the object\n"
  1121. "@return true if successful, false otherwise\n\n"
  1122. "@tsexample\n"
  1123. "%this.renameObject( \"MyBox\", \"Box\" );\n"
  1124. "@endtsexample\n")
  1125. {
  1126. if (!mShape->renameObject(oldName, newName))
  1127. return false;
  1128. ADD_TO_CHANGE_SET();
  1129. return true;
  1130. }}
  1131. DefineTSShapeConstructorMethod(removeObject, bool, (const char* name), ,
  1132. (name), false,
  1133. "Remove an object (including all meshes for that object) from the shape.\n"
  1134. "@param name name of the object to remove.\n"
  1135. "@return true if successful, false otherwise.\n\n"
  1136. "@tsexample\n"
  1137. "// clear all objects in the shape\n"
  1138. "%count = %this.getObjectCount();\n"
  1139. "for ( %i = %count-1; %i >= 0; %i-- )\n"
  1140. " %this.removeObject( %this.getObjectName(%i) );\n"
  1141. "@endtsexample\n")
  1142. {
  1143. if (!mShape->removeObject(name))
  1144. return false;
  1145. ADD_TO_CHANGE_SET();
  1146. return true;
  1147. }}
  1148. //-----------------------------------------------------------------------------
  1149. // MESHES
  1150. DefineTSShapeConstructorMethod(getMeshCount, S32, (const char* name), ,
  1151. (name), 0,
  1152. "Get the number of meshes (detail levels) for the specified object.\n"
  1153. "@param name name of the object to query\n"
  1154. "@return the number of meshes for this object.\n\n"
  1155. "@tsexample\n"
  1156. "%count = %this.getMeshCount( \"SimpleShape\" );\n"
  1157. "@endtsexample\n")
  1158. {
  1159. GET_OBJECT(getMeshCount, obj, name, 0);
  1160. Vector<S32> objectDetails;
  1161. mShape->getObjectDetails(objIndex, objectDetails);
  1162. return objectDetails.size();
  1163. }}
  1164. DefineTSShapeConstructorMethod(getMeshName, const char*, (const char* name, S32 index), ,
  1165. (name, index), "",
  1166. "Get the name of the indexed mesh (detail level) for the specified object.\n"
  1167. "@param name name of the object to query\n"
  1168. "@param index index of the mesh (valid range is 0 - getMeshCount()-1)\n"
  1169. "@return the mesh name.\n\n"
  1170. "@tsexample\n"
  1171. "// print the names of all meshes in the shape\n"
  1172. "%objCount = %this.getObjectCount();\n"
  1173. "for ( %i = 0; %i < %objCount; %i++ )\n"
  1174. "{\n"
  1175. " %objName = %this.getObjectName( %i );\n"
  1176. " %meshCount = %this.getMeshCount( %objName );\n"
  1177. " for ( %j = 0; %j < %meshCount; %j++ )\n"
  1178. " echo( %this.getMeshName( %objName, %j ) );\n"
  1179. "}\n"
  1180. "@endtsexample\n")
  1181. {
  1182. GET_OBJECT(getMeshName, obj, name, "");
  1183. Vector<S32> objectDetails;
  1184. mShape->getObjectDetails(objIndex, objectDetails);
  1185. CHECK_INDEX_IN_RANGE(getMeshName, index, objectDetails.size(), "");
  1186. static const U32 bufSize = 256;
  1187. char* returnBuffer = Con::getReturnBuffer(bufSize);
  1188. dSprintf(returnBuffer, bufSize, "%s %d", name, (S32)mShape->details[objectDetails[index]].size);
  1189. return returnBuffer;
  1190. }}
  1191. DefineTSShapeConstructorMethod(getMeshSize, S32, (const char* name, S32 index), ,
  1192. (name, index), -1,
  1193. "Get the detail level size of the indexed mesh for the specified object.\n"
  1194. "@param name name of the object to query\n"
  1195. "@param index index of the mesh (valid range is 0 - getMeshCount()-1)\n"
  1196. "@return the mesh detail level size.\n\n"
  1197. "@tsexample\n"
  1198. "// print sizes for all detail levels of this object\n"
  1199. "%objName = \"trunk\";\n"
  1200. "%count = %this.getMeshCount( %objName );\n"
  1201. "for ( %i = 0; %i < %count; %i++ )\n"
  1202. " echo( %this.getMeshSize( %objName, %i ) );\n"
  1203. "@endtsexample\n")
  1204. {
  1205. GET_OBJECT(getMeshName, obj, name, -1);
  1206. Vector<S32> objectDetails;
  1207. mShape->getObjectDetails(objIndex, objectDetails);
  1208. CHECK_INDEX_IN_RANGE(getMeshName, index, objectDetails.size(), -1);
  1209. return (S32)mShape->details[objectDetails[index]].size;
  1210. }}
  1211. DefineTSShapeConstructorMethod(setMeshSize, bool, (const char* name, S32 size), ,
  1212. (name, size), false,
  1213. "Change the detail level size of the named mesh.\n"
  1214. "@param name full name (object name + current size ) of the mesh to modify\n"
  1215. "@param size new detail level size\n"
  1216. "@return true if successful, false otherwise.\n\n"
  1217. "@tsexample\n"
  1218. "%this.setMeshSize( \"SimpleShape128\", 64 );\n"
  1219. "@endtsexample\n")
  1220. {
  1221. if (!mShape->setMeshSize(name, size))
  1222. return false;
  1223. ADD_TO_CHANGE_SET();
  1224. return true;
  1225. }}
  1226. DefineTSShapeConstructorMethod(getMeshType, const char*, (const char* name), ,
  1227. (name), "",
  1228. "Get the display type of the mesh.\n"
  1229. "@param name name of the mesh to query\n"
  1230. "@return the string returned is one of:"
  1231. "<dl><dt>normal</dt><dd>a normal 3D mesh</dd>"
  1232. "<dt>billboard</dt><dd>a mesh that always faces the camera</dd>"
  1233. "<dt>billboardzaxis</dt><dd>a mesh that always faces the camera in the Z-axis</dd></dl>\n\n"
  1234. "@tsexample\n"
  1235. "echo( \"Mesh type is \" @ %this.getMeshType( \"SimpleShape128\" ) );\n"
  1236. "@endtsexample\n")
  1237. {
  1238. GET_MESH(getMeshType, mesh, name, "normal");
  1239. if (mesh->getFlags(TSMesh::BillboardZAxis))
  1240. return "billboardzaxis";
  1241. else if (mesh->getFlags(TSMesh::Billboard))
  1242. return "billboard";
  1243. else
  1244. return "normal";
  1245. }}
  1246. DefineTSShapeConstructorMethod(setMeshType, bool, (const char* name, const char* type), ,
  1247. (name, type), false,
  1248. "Set the display type for the mesh.\n"
  1249. "@param name full name (object name + detail size) of the mesh to modify\n"
  1250. "@param type the new type for the mesh: \"normal\", \"billboard\" or \"billboardzaxis\"\n"
  1251. "@return true if successful, false otherwise\n\n"
  1252. "@tsexample\n"
  1253. "// set the mesh to be a billboard\n"
  1254. "%this.setMeshType( \"SimpleShape64\", \"billboard\" );\n"
  1255. "@endtsexample\n")
  1256. {
  1257. GET_MESH(setMeshType, mesh, name, false);
  1258. // Update the mesh flags
  1259. mesh->clearFlags(TSMesh::Billboard | TSMesh::BillboardZAxis);
  1260. if (dStrEqual(type, "billboard"))
  1261. mesh->setFlags(TSMesh::Billboard);
  1262. else if (dStrEqual(type, "billboardzaxis"))
  1263. mesh->setFlags(TSMesh::Billboard | TSMesh::BillboardZAxis);
  1264. else if (!dStrEqual(type, "normal"))
  1265. {
  1266. Con::printf("setMeshType: Unknown mesh type '%s'", type);
  1267. return false;
  1268. }
  1269. ADD_TO_CHANGE_SET();
  1270. return true;
  1271. }}
  1272. DefineTSShapeConstructorMethod(getMeshMaterial, const char*, (const char* name), ,
  1273. (name), "",
  1274. "Get the name of the material attached to a mesh. Note that only the first "
  1275. "material used by the mesh is returned.\n"
  1276. "@param name full name (object name + detail size) of the mesh to query\n"
  1277. "@return name of the material attached to the mesh (suitable for use with the Material mapTo field)\n\n"
  1278. "@tsexample\n"
  1279. "echo( \"Mesh material is \" @ %this.sgetMeshMaterial( \"SimpleShape128\" ) );\n"
  1280. "@endtsexample\n")
  1281. {
  1282. GET_MESH(getMeshMaterial, mesh, name, "");
  1283. // Return the name of the first material attached to this mesh
  1284. S32 matIndex = mesh->mPrimitives[0].matIndex & TSDrawPrimitive::MaterialMask;
  1285. if ((matIndex >= 0) && (matIndex < mShape->materialList->size()))
  1286. return mShape->materialList->getMaterialName(matIndex);
  1287. else
  1288. return "";
  1289. }}
  1290. DefineTSShapeConstructorMethod(setMeshMaterial, bool, (const char* meshName, const char* matName), ,
  1291. (meshName, matName), false,
  1292. "Set the name of the material attached to the mesh.\n"
  1293. "@param meshName full name (object name + detail size) of the mesh to modify\n"
  1294. "@param matName name of the material to attach. This could be the base name of "
  1295. "the diffuse texture (eg. \"test_mat\" for \"test_mat.jpg\"), or the name of a "
  1296. "Material object already defined in script.\n"
  1297. "@return true if successful, false otherwise\n\n"
  1298. "@tsexample\n"
  1299. "// set the mesh material\n"
  1300. "%this.setMeshMaterial( \"SimpleShape128\", \"test_mat\" );\n"
  1301. "@endtsexample\n")
  1302. {
  1303. GET_MESH(setMeshMaterial, mesh, meshName, false);
  1304. // Check if this material is already in the shape
  1305. S32 matIndex;
  1306. for (matIndex = 0; matIndex < mShape->materialList->size(); matIndex++)
  1307. {
  1308. if (dStrEqual(matName, mShape->materialList->getMaterialName(matIndex)))
  1309. break;
  1310. }
  1311. if (matIndex == mShape->materialList->size())
  1312. {
  1313. // Add a new material to the shape
  1314. U32 flags = TSMaterialList::S_Wrap | TSMaterialList::T_Wrap;
  1315. mShape->materialList->push_back(matName, flags);
  1316. }
  1317. // Set this material for all primitives in the mesh
  1318. for (S32 i = 0; i < mesh->mPrimitives.size(); i++)
  1319. {
  1320. U32 matType = mesh->mPrimitives[i].matIndex & (TSDrawPrimitive::TypeMask | TSDrawPrimitive::Indexed);
  1321. mesh->mPrimitives[i].matIndex = (matType | matIndex);
  1322. }
  1323. ADD_TO_CHANGE_SET();
  1324. return true;
  1325. }}
  1326. DefineTSShapeConstructorMethod(addMesh, bool, (const char* meshName, const char* srcShape, const char* srcMesh), ,
  1327. (meshName, srcShape, srcMesh), false,
  1328. "Add geometry from another DTS or DAE shape file into this shape.\n"
  1329. "Any materials required by the source mesh are also copied into this shape.<br>\n"
  1330. "@param meshName full name (object name + detail size) of the new mesh. If "
  1331. "no detail size is present at the end of the name, a value of 2 is used.<br>"
  1332. "An underscore before the number at the end of the name will be interpreted as "
  1333. "a negative sign. eg. \"MyMesh_4\" will be interpreted as \"MyMesh-4\".\n"
  1334. "@param srcShape name of a shape file (DTS or DAE) that contains the mesh\n"
  1335. "@param srcMesh the full name (object name + detail size) of the mesh to "
  1336. "copy from the DTS/DAE file into this shape</li>"
  1337. "@return true if successful, false otherwise\n\n"
  1338. "@tsexample\n"
  1339. "%this.addMesh( \"ColMesh-1\", \"./collision.dts\", \"ColMesh\", \"Col-1\" );\n"
  1340. "%this.addMesh( \"SimpleShape10\", \"./testShape.dae\", \"MyMesh2\", "" );\n"
  1341. "@endtsexample\n")
  1342. {
  1343. // Load the shape source file
  1344. char filenameBuf[1024];
  1345. Con::expandScriptFilename(filenameBuf, sizeof(filenameBuf), srcShape);
  1346. Resource<TSShape> hSrcShape = ResourceManager::get().load(filenameBuf);
  1347. if (!bool(hSrcShape))
  1348. {
  1349. Con::errorf("addMesh failed: Could not load source shape: '%s'", filenameBuf);
  1350. return false;
  1351. }
  1352. TSShape* shape = const_cast<TSShape*>((const TSShape*)hSrcShape);
  1353. if (!mShape->addMesh(shape, srcMesh, meshName))
  1354. return false;
  1355. ADD_TO_CHANGE_SET();
  1356. return true;
  1357. }}
  1358. DefineTSShapeConstructorMethod(removeMesh, bool, (const char* name), ,
  1359. (name), false,
  1360. "Remove a mesh from the shape.\n"
  1361. "If all geometry is removed from an object, the object is also removed.\n"
  1362. "@param name full name (object name + detail size) of the mesh to remove\n"
  1363. "@return true if successful, false otherwise\n\n"
  1364. "@tsexample\n"
  1365. "%this.removeMesh( \"SimpleShape128\" );\n"
  1366. "@endtsexample\n")
  1367. {
  1368. if (!mShape->removeMesh(name))
  1369. return false;
  1370. ADD_TO_CHANGE_SET();
  1371. return true;
  1372. }}
  1373. DefineTSShapeConstructorMethod(getBounds, Box3F, (), ,
  1374. (), Box3F::Invalid,
  1375. "Get the bounding box for the shape.\n"
  1376. "@return Bounding box \"minX minY minZ maxX maxY maxZ\"")
  1377. {
  1378. return mShape->mBounds;
  1379. }}
  1380. DefineTSShapeConstructorMethod(setBounds, bool, (Box3F bbox), ,
  1381. (bbox), false,
  1382. "Set the shape bounds to the given bounding box.\n"
  1383. "@param Bounding box \"minX minY minZ maxX maxY maxZ\"\n"
  1384. "@return true if successful, false otherwise\n")
  1385. {
  1386. // Set shape bounds
  1387. TSShape* shape = mShape;
  1388. shape->mBounds = bbox;
  1389. shape->mBounds.getCenter(&shape->center);
  1390. shape->mRadius = (shape->mBounds.maxExtents - shape->center).len();
  1391. shape->tubeRadius = shape->mRadius;
  1392. ADD_TO_CHANGE_SET();
  1393. return true;
  1394. }}
  1395. //-----------------------------------------------------------------------------
  1396. // DETAILS
  1397. DefineTSShapeConstructorMethod(getDetailLevelCount, S32, (), , (), 0,
  1398. "Get the total number of detail levels in the shape.\n"
  1399. "@return the number of detail levels in the shape\n")
  1400. {
  1401. return mShape->details.size();
  1402. }}
  1403. DefineTSShapeConstructorMethod(getDetailLevelName, const char*, (S32 index), ,
  1404. (index), "",
  1405. "Get the name of the indexed detail level.\n"
  1406. "@param index detail level index (valid range is 0 - getDetailLevelCount()-1)\n"
  1407. "@return the detail level name\n\n"
  1408. "@tsexample\n"
  1409. "// print the names of all detail levels in the shape\n"
  1410. "%count = %this.getDetailLevelCount();\n"
  1411. "for ( %i = 0; %i < %count; %i++ )\n"
  1412. " echo( %i SPC %this.getDetailLevelName( %i ) );\n"
  1413. "@endtsexample\n")
  1414. {
  1415. CHECK_INDEX_IN_RANGE(getDetailLevelName, index, mShape->details.size(), "");
  1416. return mShape->getName(mShape->details[index].nameIndex);
  1417. }}
  1418. DefineTSShapeConstructorMethod(getDetailLevelSize, S32, (S32 index), ,
  1419. (index), 0,
  1420. "Get the size of the indexed detail level.\n"
  1421. "@param index detail level index (valid range is 0 - getDetailLevelCount()-1)\n"
  1422. "@return the detail level size\n\n"
  1423. "@tsexample\n"
  1424. "// print the sizes of all detail levels in the shape\n"
  1425. "%count = %this.getDetailLevelCount();\n"
  1426. "for ( %i = 0; %i < %count; %i++ )\n"
  1427. " echo( \"Detail\" @ %i @ \" has size \" @ %this.getDetailLevelSize( %i ) );\n"
  1428. "@endtsexample\n")
  1429. {
  1430. CHECK_INDEX_IN_RANGE(getDetailLevelSize, index, mShape->details.size(), 0);
  1431. return (S32)mShape->details[index].size;
  1432. }}
  1433. DefineTSShapeConstructorMethod(getDetailLevelIndex, S32, (S32 size), ,
  1434. (size), -1,
  1435. "Get the index of the detail level with a given size.\n"
  1436. "@param size size of the detail level to lookup\n"
  1437. "@return index of the detail level with the desired size, or -1 if no such "
  1438. "detail exists\n\n"
  1439. "@tsexample\n"
  1440. "if ( %this.getDetailLevelSize( 32 ) == -1 )\n"
  1441. " echo( \"Error: This shape does not have a detail level at size 32\" );\n"
  1442. "@endtsexample\n")
  1443. {
  1444. return mShape->findDetailBySize(size);
  1445. }}
  1446. DefineTSShapeConstructorMethod(renameDetailLevel, bool, (const char* oldName, const char* newName), ,
  1447. (oldName, newName), false,
  1448. "Rename a detail level.\n"
  1449. "@note Note that detail level names must be unique, so this command will "
  1450. "fail if there is already a detail level with the desired name\n"
  1451. "@param oldName current name of the detail level\n"
  1452. "@param newName new name of the detail level\n"
  1453. "@return true if successful, false otherwise\n\n"
  1454. "@tsexample\n"
  1455. "%this.renameDetailLevel( \"detail-1\", \"collision-1\" );\n"
  1456. "@endtsexample\n")
  1457. {
  1458. if (!mShape->renameDetail(oldName, newName))
  1459. return false;
  1460. ADD_TO_CHANGE_SET();
  1461. return true;
  1462. }}
  1463. DefineTSShapeConstructorMethod(removeDetailLevel, bool, (S32 index), ,
  1464. (index), false,
  1465. "Remove the detail level (including all meshes in the detail level)\n"
  1466. "@param size size of the detail level to remove\n"
  1467. "@return true if successful, false otherwise\n"
  1468. "@tsexample\n"
  1469. "%this.removeDetailLevel( 2 );\n"
  1470. "@endtsexample\n")
  1471. {
  1472. if (!mShape->removeDetail(index))
  1473. return false;
  1474. ADD_TO_CHANGE_SET();
  1475. return true;
  1476. }}
  1477. DefineTSShapeConstructorMethod(setDetailLevelSize, S32, (S32 index, S32 newSize), ,
  1478. (index, newSize), index,
  1479. "Change the size of a detail level."
  1480. "@note Note that detail levels are always sorted in decreasing size order, "
  1481. "so this command may cause detail level indices to change.\n"
  1482. "@param index index of the detail level to modify\n"
  1483. "@param newSize new size for the detail level\n"
  1484. "@return new index for this detail level\n\n"
  1485. "@tsexample\n"
  1486. "%this.setDetailLevelSize( 2, 256 );\n"
  1487. "@endtsexample\n")
  1488. {
  1489. S32 dl = mShape->setDetailSize(index, newSize);
  1490. if (dl >= 0)
  1491. ADD_TO_CHANGE_SET();
  1492. return dl;
  1493. }}
  1494. DefineTSShapeConstructorMethod(getImposterDetailLevel, S32, (), , (), -1,
  1495. "Get the index of the imposter (auto-billboard) detail level (if any).\n"
  1496. "@return imposter detail level index, or -1 if the shape does not use "
  1497. "imposters.\n\n")
  1498. {
  1499. for (S32 i = 0; i < mShape->details.size(); i++)
  1500. {
  1501. if (mShape->details[i].subShapeNum < 0)
  1502. return i;
  1503. }
  1504. return -1;
  1505. }}
  1506. DefineTSShapeConstructorMethod(getImposterSettings, const char*, (S32 index), ,
  1507. (index), "",
  1508. "Get the settings used to generate imposters for the indexed detail level.\n"
  1509. "@param index index of the detail level to query (does not need to be an "
  1510. "imposter detail level\n"
  1511. "@return string of the form: \"valid eqSteps pSteps dl dim poles angle\", where:"
  1512. "<dl>"
  1513. "<dt>valid</dt><dd>1 if this detail level generates imposters, 0 otherwise</dd>"
  1514. "<dt>eqSteps</dt><dd>number of steps around the equator</dd>"
  1515. "<dt>pSteps</dt><dd>number of steps between the poles</dd>"
  1516. "<dt>dl</dt><dd>index of the detail level used to generate imposters</dd>"
  1517. "<dt>dim</dt><dd>size (in pixels) of each imposter image</dd>"
  1518. "<dt>poles</dt><dd>1 to include pole images, 0 otherwise</dd>"
  1519. "<dt>angle</dt><dd>angle at which to display pole images</dd>"
  1520. "</dl>\n\n"
  1521. "@tsexample\n"
  1522. "// print the imposter detail level settings\n"
  1523. "%index = %this.getImposterDetailLevel();\n"
  1524. "if ( %index != -1 )\n"
  1525. " echo( \"Imposter settings: \" @ %this.getImposterSettings( %index ) );\n"
  1526. "@endtsexample\n")
  1527. {
  1528. CHECK_INDEX_IN_RANGE(getImposterSettings, index, mShape->details.size(), "");
  1529. // Return information about the detail level
  1530. const TSShape::Detail& det = mShape->details[index];
  1531. static const U32 bufSize = 512;
  1532. char* returnBuffer = Con::getReturnBuffer(bufSize);
  1533. dSprintf(returnBuffer, bufSize, "%d\t%d\t%d\t%d\t%d\t%d\t%g",
  1534. (S32)(det.subShapeNum < 0), // isImposter
  1535. det.bbEquatorSteps,
  1536. det.bbPolarSteps,
  1537. det.bbDetailLevel,
  1538. det.bbDimension,
  1539. det.bbIncludePoles,
  1540. det.bbPolarAngle);
  1541. return returnBuffer;
  1542. }}
  1543. DefineTSShapeConstructorMethod(addImposter, S32, (S32 size, S32 equatorSteps, S32 polarSteps, S32 dl, S32 dim, bool includePoles, F32 polarAngle), ,
  1544. (size, equatorSteps, polarSteps, dl, dim, includePoles, polarAngle), -1,
  1545. "Add (or edit) an imposter detail level to the shape.\n"
  1546. "If the shape already contains an imposter detail level, this command will "
  1547. "simply change the imposter settings\n"
  1548. "@param size size of the imposter detail level\n"
  1549. "@param equatorSteps defines the number of snapshots to take around the "
  1550. "equator. Imagine the object being rotated around the vertical axis, then "
  1551. "a snapshot taken at regularly spaced intervals.\n"
  1552. "@param polarSteps defines the number of snapshots taken between the poles "
  1553. "(top and bottom), at each equator step. eg. At each equator snapshot, "
  1554. "snapshots are taken at regular intervals between the poles.\n"
  1555. "@param dl the detail level to use when generating the snapshots. Note that "
  1556. "this is an array index rather than a detail size. So if an object has detail "
  1557. "sizes of: 200, 150, and 40, then setting @a dl to 1 will generate the snapshots "
  1558. "using detail size 150.\n"
  1559. "@param dim defines the size of the imposter images in pixels. The larger the "
  1560. "number, the more detailed the billboard will be.\n"
  1561. "@param includePoles flag indicating whether to include the \"pole\" snapshots. "
  1562. "ie. the views from the top and bottom of the object.\n"
  1563. "@param polar_angle if pole snapshots are active (@a includePoles is true), this "
  1564. "parameter defines the camera angle (in degrees) within which to render the "
  1565. "pole snapshot. eg. if polar_angle is set to 25 degrees, then the snapshot "
  1566. "taken at the pole (looking directly down or up at the object) will be rendered "
  1567. "when the camera is within 25 degrees of the pole.\n"
  1568. "@return true if successful, false otherwise\n\n"
  1569. "@tsexample\n"
  1570. "%this.addImposter( 2, 4, 0, 0, 64, false, 0 );\n"
  1571. "%this.addImposter( 2, 4, 2, 0, 64, true, 10 ); // this command would edit the existing imposter detail level\n"
  1572. "@endtsexample\n")
  1573. {
  1574. // Add the imposter detail level
  1575. dl = mShape->addImposter(getShapePath(), size, equatorSteps, polarSteps, dl, dim, includePoles, polarAngle);
  1576. if (dl != -1)
  1577. ADD_TO_CHANGE_SET();
  1578. return dl;
  1579. }}
  1580. DefineTSShapeConstructorMethod(removeImposter, bool, (), , (), false,
  1581. "() Remove the imposter detail level (if any) from the shape.\n"
  1582. "@return true if successful, false otherwise\n\n")
  1583. {
  1584. if (!mShape->removeImposter())
  1585. return false;
  1586. ADD_TO_CHANGE_SET();
  1587. return true;
  1588. }}
  1589. //-----------------------------------------------------------------------------
  1590. // SEQUENCES
  1591. DefineTSShapeConstructorMethod(getSequenceCount, S32, (), , (), 0,
  1592. "Get the total number of sequences in the shape.\n"
  1593. "@return the number of sequences in the shape\n\n")
  1594. {
  1595. return mShape->sequences.size();
  1596. }}
  1597. DefineTSShapeConstructorMethod(getSequenceIndex, S32, (const char* name), ,
  1598. (name), -1,
  1599. "Find the index of the sequence with the given name.\n"
  1600. "@param name name of the sequence to lookup\n"
  1601. "@return index of the sequence with matching name, or -1 if not found\n\n"
  1602. "@tsexample\n"
  1603. "// Check if a given sequence exists in the shape\n"
  1604. "if ( %this.getSequenceIndex( \"walk\" ) == -1 )\n"
  1605. " echo( \"Could not find 'walk' sequence\" );\n"
  1606. "@endtsexample\n")
  1607. {
  1608. return mShape->findSequence(name);
  1609. }}
  1610. DefineTSShapeConstructorMethod(getSequenceName, const char*, (S32 index), ,
  1611. (index), "",
  1612. "Get the name of the indexed sequence.\n"
  1613. "@param index index of the sequence to query (valid range is 0 - getSequenceCount()-1)\n"
  1614. "@return the name of the sequence\n\n"
  1615. "@tsexample\n"
  1616. "// print the name of all sequences in the shape\n"
  1617. "%count = %this.getSequenceCount();\n"
  1618. "for ( %i = 0; %i < %count; %i++ )\n"
  1619. " echo( %i SPC %this.getSequenceName( %i ) );\n"
  1620. "@endtsexample\n")
  1621. {
  1622. CHECK_INDEX_IN_RANGE(getSequenceName, index, mShape->sequences.size(), "");
  1623. return mShape->getName(mShape->sequences[index].nameIndex);
  1624. }}
  1625. DefineTSShapeConstructorMethod(getSequenceSource, const char*, (const char* name), ,
  1626. (name), "",
  1627. "Get information about where the sequence data came from.\n"
  1628. "For example, whether it was loaded from an external DSQ file.\n"
  1629. "@param name name of the sequence to query\n"
  1630. "@return TAB delimited string of the form: \"from reserved start end total\", where:"
  1631. "<dl>"
  1632. "<dt>from</dt><dd>the source of the animation data, such as the path to "
  1633. "a DSQ file, or the name of an existing sequence in the shape. This field "
  1634. "will be empty for sequences already embedded in the DTS or DAE file.</dd>"
  1635. "<dt>reserved</dt><dd>reserved value</dd>"
  1636. "<dt>start</dt><dd>the first frame in the source sequence used to create this sequence</dd>"
  1637. "<dt>end</dt><dd>the last frame in the source sequence used to create this sequence</dd>"
  1638. "<dt>total</dt><dd>the total number of frames in the source sequence</dd>"
  1639. "</dl>\n\n"
  1640. "@tsexample\n"
  1641. "// print the source for the walk animation\n"
  1642. "echo( \"walk source:\" SPC getField( %this.getSequenceSource( \"walk\" ), 0 ) );\n"
  1643. "@endtsexample\n")
  1644. {
  1645. GET_SEQUENCE(getSequenceSource, seq, name, "");
  1646. // Return information about the source data for this sequence
  1647. static const U32 bufSize = 512;
  1648. char* returnBuffer = Con::getReturnBuffer(bufSize);
  1649. dSprintf(returnBuffer, bufSize, "%s\t%d\t%d\t%d",
  1650. seq->sourceData.from.c_str(), seq->sourceData.start,
  1651. seq->sourceData.end, seq->sourceData.total);
  1652. return returnBuffer;
  1653. }}
  1654. DefineTSShapeConstructorMethod(getSequenceFrameCount, S32, (const char* name), ,
  1655. (name), 0,
  1656. "Get the number of keyframes in the sequence.\n"
  1657. "@param name name of the sequence to query\n"
  1658. "@return number of keyframes in the sequence\n\n"
  1659. "@tsexample\n"
  1660. "echo( \"Run has \" @ %this.getSequenceFrameCount( \"run\" ) @ \" keyframes\" );\n"
  1661. "@endtsexample\n")
  1662. {
  1663. GET_SEQUENCE(getSequenceFrameCount, seq, name, 0);
  1664. return seq->numKeyframes;
  1665. }}
  1666. DefineTSShapeConstructorMethod(getSequencePriority, F32, (const char* name), ,
  1667. (name), -1.0f,
  1668. "Get the priority setting of the sequence.\n"
  1669. "@param name name of the sequence to query\n"
  1670. "@return priority value of the sequence\n\n")
  1671. {
  1672. GET_SEQUENCE(getSequencePriority, seq, name, 0.0f);
  1673. return seq->priority;
  1674. }}
  1675. DefineTSShapeConstructorMethod(setSequencePriority, bool, (const char* name, F32 priority), ,
  1676. (name, priority), false,
  1677. "Set the sequence priority.\n"
  1678. "@param name name of the sequence to modify\n"
  1679. "@param priority new priority value\n"
  1680. "@return true if successful, false otherwise\n\n")
  1681. {
  1682. GET_SEQUENCE(setSequencePriority, seq, name, false);
  1683. seq->priority = priority;
  1684. ADD_TO_CHANGE_SET();
  1685. return true;
  1686. }}
  1687. DefineTSShapeConstructorMethod(getSequenceGroundSpeed, const char*, (const char* name), ,
  1688. (name), "",
  1689. "Get the ground speed of the sequence.\n"
  1690. "@note Note that only the first 2 ground frames of the sequence are "
  1691. "examined; the speed is assumed to be constant throughout the sequence.\n"
  1692. "@param name name of the sequence to query\n"
  1693. "@return string of the form: \"trans.x trans.y trans.z rot.x rot.y rot.z\"\n\n"
  1694. "@tsexample\n"
  1695. "%speed = VectorLen( getWords( %this.getSequenceGroundSpeed( \"run\" ), 0, 2 ) );\n"
  1696. " echo( \"Run moves at \" @ %speed @ \" units per frame\" );\n"
  1697. "@endtsexample\n")
  1698. {
  1699. // Find the sequence and return the ground speed (assumed to be constant)
  1700. GET_SEQUENCE(getSequenceGroundSpeed, seq, name, "");
  1701. Point3F trans(0, 0, 0), rot(0, 0, 0);
  1702. if (seq->numGroundFrames > 0)
  1703. {
  1704. const Point3F& p1 = mShape->groundTranslations[seq->firstGroundFrame];
  1705. const Point3F& p2 = mShape->groundTranslations[seq->firstGroundFrame + 1];
  1706. trans = p2 - p1;
  1707. QuatF r1 = mShape->groundRotations[seq->firstGroundFrame].getQuatF();
  1708. QuatF r2 = mShape->groundRotations[seq->firstGroundFrame + 1].getQuatF();
  1709. r2 -= r1;
  1710. MatrixF mat;
  1711. r2.setMatrix(&mat);
  1712. rot = mat.toEuler();
  1713. }
  1714. static const U32 bufSize = 256;
  1715. char* returnBuffer = Con::getReturnBuffer(bufSize);
  1716. dSprintf(returnBuffer, bufSize, "%g %g %g %g %g %g",
  1717. trans.x, trans.y, trans.z, rot.x, rot.y, rot.z);
  1718. return returnBuffer;
  1719. }}
  1720. DefineTSShapeConstructorMethod(setSequenceGroundSpeed, bool, (const char* name, Point3F transSpeed, Point3F rotSpeed), (Point3F::Zero),
  1721. (name, transSpeed, rotSpeed), false,
  1722. "Set the translation and rotation ground speed of the sequence.\n"
  1723. "The ground speed of the sequence is set by generating ground transform "
  1724. "keyframes. The ground translational and rotational speed is assumed to "
  1725. "be constant for the duration of the sequence. Existing ground frames for "
  1726. "the sequence (if any) will be replaced.\n"
  1727. "@param name name of the sequence to modify\n"
  1728. "@param transSpeed translational speed (trans.x trans.y trans.z) in "
  1729. "Torque units per frame\n"
  1730. "@param rotSpeed (optional) rotational speed (rot.x rot.y rot.z) in "
  1731. "radians per frame. Default is \"0 0 0\"\n"
  1732. "@return true if successful, false otherwise\n\n"
  1733. "@tsexample\n"
  1734. "%this.setSequenceGroundSpeed( \"run\", \"5 0 0\" );\n"
  1735. "%this.setSequenceGroundSpeed( \"spin\", \"0 0 0\", \"4 0 0\" );\n"
  1736. "@endtsexample\n")
  1737. {
  1738. if (!mShape->setSequenceGroundSpeed(name, transSpeed, rotSpeed))
  1739. return false;
  1740. ADD_TO_CHANGE_SET();
  1741. return true;
  1742. }}
  1743. DefineTSShapeConstructorMethod(getSequenceCyclic, bool, (const char* name), ,
  1744. (name), false,
  1745. "Check if this sequence is cyclic (looping).\n"
  1746. "@param name name of the sequence to query\n"
  1747. "@return true if this sequence is cyclic, false if not\n\n"
  1748. "@tsexample\n"
  1749. "if ( !%this.getSequenceCyclic( \"ambient\" ) )\n"
  1750. " error( \"ambient sequence is not cyclic!\" );\n"
  1751. "@endtsexample\n")
  1752. {
  1753. GET_SEQUENCE(getSequenceCyclic, seq, name, false);
  1754. return seq->isCyclic();
  1755. }}
  1756. DefineTSShapeConstructorMethod(setSequenceCyclic, bool, (const char* name, bool cyclic), ,
  1757. (name, cyclic), false,
  1758. "Mark a sequence as cyclic or non-cyclic.\n"
  1759. "@param name name of the sequence to modify\n"
  1760. "@param cyclic true to make the sequence cyclic, false for non-cyclic\n"
  1761. "@return true if successful, false otherwise\n\n"
  1762. "@tsexample\n"
  1763. "%this.setSequenceCyclic( \"ambient\", true );\n"
  1764. "%this.setSequenceCyclic( \"shoot\", false );\n"
  1765. "@endtsexample\n")
  1766. {
  1767. GET_SEQUENCE(setSequenceCyclic, seq, name, false);
  1768. // update cyclic flag
  1769. if (cyclic != seq->isCyclic())
  1770. {
  1771. if (cyclic && !seq->isCyclic())
  1772. seq->flags |= TSShape::Cyclic;
  1773. else if (!cyclic && seq->isCyclic())
  1774. seq->flags &= (~(TSShape::Cyclic));
  1775. ADD_TO_CHANGE_SET();
  1776. }
  1777. return true;
  1778. }}
  1779. DefineTSShapeConstructorMethod(getSequenceBlend, const char*, (const char* name), ,
  1780. (name), "",
  1781. "Get information about blended sequences.\n"
  1782. "@param name name of the sequence to query\n"
  1783. "@return TAB delimited string of the form: \"isBlend blendSeq blendFrame\", where:"
  1784. "<dl>"
  1785. "<dt>blend_flag</dt><dd>a boolean flag indicating whether this sequence is a blend</dd>"
  1786. "<dt>blend_seq_name</dt><dd>the name of the sequence that contains the reference "
  1787. "frame (empty for blend sequences embedded in DTS files)</dd>"
  1788. "<dt>blend_seq_frame</dt><dd>the blend reference frame (empty for blend sequences "
  1789. "embedded in DTS files)</dd>"
  1790. "</dl>\n"
  1791. "@note Note that only sequences set to be blends using the setSequenceBlend "
  1792. "command will contain the blendSeq and blendFrame information.\n\n"
  1793. "@tsexample\n"
  1794. "%blendData = %this.getSequenceBlend( \"look\" );\n"
  1795. "if ( getField( %blendData, 0 ) )\n"
  1796. " echo( \"look is a blend, reference: \" @ getField( %blendData, 1 ) );\n"
  1797. "@endtsexample\n")
  1798. {
  1799. GET_SEQUENCE(getSequenceBlend, seq, name, "0");
  1800. // Return the blend information (flag reference_sequence reference_frame)
  1801. static const U32 bufSize = 512;
  1802. char* returnBuffer = Con::getReturnBuffer(bufSize);
  1803. dSprintf(returnBuffer, bufSize, "%d\t%s\t%d", (int)seq->isBlend(),
  1804. seq->sourceData.blendSeq.c_str(), seq->sourceData.blendFrame);
  1805. return returnBuffer;
  1806. }}
  1807. DefineTSShapeConstructorMethod(setSequenceBlend, bool, (const char* name, bool blend, const char* blendSeq, S32 blendFrame), ,
  1808. (name, blend, blendSeq, blendFrame), false,
  1809. "Mark a sequence as a blend or non-blend.\n"
  1810. "A blend sequence is one that will be added on top of any other playing "
  1811. "sequences. This is done by storing the animated node transforms relative "
  1812. "to a reference frame, rather than as absolute transforms.\n"
  1813. "@param name name of the sequence to modify\n"
  1814. "@param blend true to make the sequence a blend, false for a non-blend\n"
  1815. "@param blendSeq the name of the sequence that contains the blend reference frame\n"
  1816. "@param blendFrame the reference frame in the blendSeq sequence\n"
  1817. "@return true if successful, false otherwise\n\n"
  1818. "@tsexample\n"
  1819. "%this.setSequenceBlend( \"look\", true, \"root\", 0 );\n"
  1820. "@endtsexample\n")
  1821. {
  1822. GET_SEQUENCE(setSequenceBlend, seq, name, false);
  1823. if (!mShape->setSequenceBlend(name, blend, blendSeq, blendFrame))
  1824. return false;
  1825. ADD_TO_CHANGE_SET();
  1826. return true;
  1827. }}
  1828. DefineTSShapeConstructorMethod(renameSequence, bool, (const char* oldName, const char* newName), ,
  1829. (oldName, newName), false,
  1830. "Rename a sequence.\n"
  1831. "@note Note that sequence names must be unique, so this command will fail "
  1832. "if there is already a sequence with the desired name\n"
  1833. "@param oldName current name of the sequence\n"
  1834. "@param newName new name of the sequence\n"
  1835. "@return true if successful, false otherwise\n\n"
  1836. "@tsexample\n"
  1837. "%this.renameSequence( \"walking\", \"walk\" );\n"
  1838. "@endtsexample\n")
  1839. {
  1840. GET_SEQUENCE(renameSequence, seq, oldName, false);
  1841. if (!mShape->renameSequence(oldName, newName))
  1842. return false;
  1843. ADD_TO_CHANGE_SET();
  1844. return true;
  1845. }}
  1846. DefineTSShapeConstructorMethod(addSequence, bool,
  1847. (const char* source, const char* name, S32 start, S32 end, bool padRot, bool padTrans),
  1848. (0, -1, true, false), (source, name, start, end, padRot, padTrans), false,
  1849. "Add a new sequence to the shape.\n"
  1850. "@param source the name of an existing sequence, or the name of a DTS or DAE "
  1851. "shape or DSQ sequence file. When the shape file contains more than one "
  1852. "sequence, the desired sequence can be specified by appending the name to the "
  1853. "end of the shape file. eg. \"myShape.dts run\" would select the \"run\" "
  1854. "sequence from the \"myShape.dts\" file.\n\n"
  1855. "@param name name of the new sequence\n"
  1856. "@param start (optional) first frame to copy. Defaults to 0, the first frame in the sequence.\n"
  1857. "@param end (optional) last frame to copy. Defaults to -1, the last frame in the sequence.\n"
  1858. "@param padRot (optional) copy root-pose rotation keys for non-animated nodes. This is useful if "
  1859. "the source sequence data has a different root-pose to the target shape, such as if one character was "
  1860. "in the T pose, and the other had arms at the side. Normally only nodes that are actually rotated by "
  1861. "the source sequence have keyframes added, but setting this flag will also add keyframes for nodes "
  1862. "that are not animated, but have a different root-pose rotation to the target shape root pose.\n"
  1863. "@param padTrans (optional) copy root-pose translation keys for non-animated nodes. This is useful if "
  1864. "the source sequence data has a different root-pose to the target shape, such as if one character was "
  1865. "in the T pose, and the other had arms at the side. Normally only nodes that are actually moved by "
  1866. "the source sequence have keyframes added, but setting this flag will also add keyframes for nodes "
  1867. "that are not animated, but have a different root-pose position to the target shape root pose.\n"
  1868. "@return true if successful, false otherwise\n\n"
  1869. "@tsexample\n"
  1870. "%this.addSequence( \"./testShape.dts ambient\", \"ambient\" );\n"
  1871. "%this.addSequence( \"./myPlayer.dae run\", \"run\" );\n"
  1872. "%this.addSequence( \"./player_look.dsq\", \"look\", 0, -1 ); // start to end\n"
  1873. "%this.addSequence( \"walk\", \"walk_shortA\", 0, 4 ); // start to frame 4\n"
  1874. "%this.addSequence( \"walk\", \"walk_shortB\", 4, -1 ); // frame 4 to end\n"
  1875. "@endtsexample\n")
  1876. {
  1877. String srcName;
  1878. String srcPath(source);
  1879. SplitSequencePathAndName(srcPath, srcName);
  1880. if (AssetDatabase.isDeclaredAsset(srcPath))
  1881. {
  1882. StringTableEntry assetId = StringTable->insert(srcPath.c_str());
  1883. StringTableEntry assetType = AssetDatabase.getAssetType(assetId);
  1884. if (assetType == StringTable->insert("ShapeAsset"))
  1885. {
  1886. ShapeAsset* asset = AssetDatabase.acquireAsset<ShapeAsset>(assetId);
  1887. srcPath = asset->getShapeFilePath();
  1888. AssetDatabase.releaseAsset(assetId);
  1889. }
  1890. else if (assetType == StringTable->insert("ShapeAnimationAsset"))
  1891. {
  1892. ShapeAnimationAsset* asset = AssetDatabase.acquireAsset<ShapeAnimationAsset>(assetId);
  1893. srcPath = asset->getAnimationPath();
  1894. AssetDatabase.releaseAsset(assetId);
  1895. }
  1896. }
  1897. if (!mShape->addSequence(srcPath, srcName, name, start, end, padRot, padTrans))
  1898. return false;
  1899. ADD_TO_CHANGE_SET();
  1900. return true;
  1901. }}
  1902. DefineTSShapeConstructorMethod(removeSequence, bool, (const char* name), ,
  1903. (name), false,
  1904. "Remove the sequence from the shape.\n"
  1905. "@param name name of the sequence to remove\n"
  1906. "@return true if successful, false otherwise\n\n")
  1907. {
  1908. if (!mShape->removeSequence(name))
  1909. return false;
  1910. ADD_TO_CHANGE_SET();
  1911. return true;
  1912. }}
  1913. //-----------------------------------------------------------------------------
  1914. // TRIGGERS
  1915. DefineTSShapeConstructorMethod(getTriggerCount, S32, (const char* name), ,
  1916. (name), 0,
  1917. "Get the number of triggers in the specified sequence.\n"
  1918. "@param name name of the sequence to query\n"
  1919. "@return number of triggers in the sequence\n\n")
  1920. {
  1921. GET_SEQUENCE(getTriggerCount, seq, name, 0);
  1922. return seq->numTriggers;
  1923. }}
  1924. DefineTSShapeConstructorMethod(getTrigger, const char*, (const char* name, S32 index), ,
  1925. (name, index), "",
  1926. "Get information about the indexed trigger\n"
  1927. "@param name name of the sequence to query\n"
  1928. "@param index index of the trigger (valid range is 0 - getTriggerCount()-1)\n"
  1929. "@return string of the form \"frame state\"\n\n"
  1930. "@tsexample\n"
  1931. "// print all triggers in the sequence\n"
  1932. "%count = %this.getTriggerCount( \"back\" );\n"
  1933. "for ( %i = 0; %i < %count; %i++ )\n"
  1934. " echo( %i SPC %this.getTrigger( \"back\", %i ) );\n"
  1935. "@endtsexample\n")
  1936. {
  1937. // Find the sequence and return the indexed trigger (frame and state)
  1938. GET_SEQUENCE(getTrigger, seq, name, "");
  1939. CHECK_INDEX_IN_RANGE(getTrigger, index, seq->numTriggers, "");
  1940. const TSShape::Trigger& trig = mShape->triggers[seq->firstTrigger + index];
  1941. S32 frame = trig.pos * seq->numKeyframes;
  1942. S32 state = getBinLog2(trig.state & TSShape::Trigger::StateMask) + 1;
  1943. if (!(trig.state & TSShape::Trigger::StateOn))
  1944. state = -state;
  1945. static const U32 bufSize = 32;
  1946. char* returnBuffer = Con::getReturnBuffer(bufSize);
  1947. dSprintf(returnBuffer, bufSize, "%d %d", frame, state);
  1948. return returnBuffer;
  1949. }}
  1950. DefineTSShapeConstructorMethod(addTrigger, bool, (const char* name, S32 keyframe, S32 state), ,
  1951. (name, keyframe, state), false,
  1952. "Add a new trigger to the sequence.\n"
  1953. "@param name name of the sequence to modify\n"
  1954. "@param keyframe keyframe of the new trigger\n"
  1955. "@param state of the new trigger\n"
  1956. "@return true if successful, false otherwise\n\n"
  1957. "@tsexample\n"
  1958. "%this.addTrigger( \"walk\", 3, 1 );\n"
  1959. "%this.addTrigger( \"walk\", 5, -1 );\n"
  1960. "@endtsexample\n")
  1961. {
  1962. if (!mShape->addTrigger(name, keyframe, state))
  1963. return false;
  1964. ADD_TO_CHANGE_SET();
  1965. return true;
  1966. }}
  1967. DefineTSShapeConstructorMethod(removeTrigger, bool, (const char* name, S32 keyframe, S32 state), ,
  1968. (name, keyframe, state), false,
  1969. "Remove a trigger from the sequence.\n"
  1970. "@param name name of the sequence to modify\n"
  1971. "@param keyframe keyframe of the trigger to remove\n"
  1972. "@param state of the trigger to remove\n"
  1973. "@return true if successful, false otherwise\n\n"
  1974. "@tsexample\n"
  1975. "%this.removeTrigger( \"walk\", 3, 1 );\n"
  1976. "@endtsexample\n")
  1977. {
  1978. if (!mShape->removeTrigger(name, keyframe, state))
  1979. return false;
  1980. ADD_TO_CHANGE_SET();
  1981. return true;
  1982. }}
  1983. DefineEngineFunction(findShapeConstructorByAssetId, S32, (const char* assetId),,
  1984. "Attempts to find an existing TSShapeConstructor by looking up an AssetId\n"
  1985. "@tsexample\n"
  1986. "findConstructorByAssetId(\"MyModule:MyShape\");\n"
  1987. "@endtsexample")
  1988. {
  1989. StringTableEntry assetIdSTE = StringTable->insert(assetId);
  1990. TSShapeConstructor* tss = TSShapeConstructor::findShapeConstructorByAssetId(assetIdSTE);
  1991. if (tss)
  1992. return tss->getId();
  1993. else
  1994. return 0;
  1995. }
  1996. DefineEngineFunction(findShapeConstructorByFilename, S32, (const char* filename),,
  1997. "Attempts to find an existing TSShapeConstructor by looking up a filename\n"
  1998. "@tsexample\n"
  1999. "findShapeConstructorByFilename(\"data/myShape.dae\");\n"
  2000. "@endtsexample")
  2001. {
  2002. FileName flName = FileName(filename);
  2003. TSShapeConstructor* tss = TSShapeConstructor::findShapeConstructorByFilename(flName);
  2004. if (tss)
  2005. return tss->getId();
  2006. else
  2007. return 0;
  2008. }
  2009. //-----------------------------------------------------------------------------
  2010. // Change-Set manipulation
  2011. TSShapeConstructor::ChangeSet::eCommandType TSShapeConstructor::ChangeSet::getCmdType(const char* name)
  2012. {
  2013. #define RETURN_IF_MATCH(type) if (!dStricmp(name, #type)) return Cmd##type
  2014. RETURN_IF_MATCH(AddNode);
  2015. else RETURN_IF_MATCH(RemoveNode);
  2016. else RETURN_IF_MATCH(RenameNode);
  2017. else RETURN_IF_MATCH(SetNodeTransform);
  2018. else RETURN_IF_MATCH(SetNodeParent);
  2019. else RETURN_IF_MATCH(AddMesh);
  2020. else RETURN_IF_MATCH(AddPrimitive);
  2021. else RETURN_IF_MATCH(SetMeshSize);
  2022. else RETURN_IF_MATCH(SetMeshType);
  2023. else RETURN_IF_MATCH(SetMeshMaterial);
  2024. else RETURN_IF_MATCH(RemoveMesh);
  2025. else RETURN_IF_MATCH(SetObjectNode);
  2026. else RETURN_IF_MATCH(RenameObject);
  2027. else RETURN_IF_MATCH(RemoveObject);
  2028. else RETURN_IF_MATCH(SetBounds);
  2029. else RETURN_IF_MATCH(SetDetailLevelSize);
  2030. else RETURN_IF_MATCH(RenameDetailLevel);
  2031. else RETURN_IF_MATCH(RemoveDetailLevel);
  2032. else RETURN_IF_MATCH(AddImposter);
  2033. else RETURN_IF_MATCH(RemoveImposter);
  2034. else RETURN_IF_MATCH(AddCollisionDetail);
  2035. else RETURN_IF_MATCH(AddSequence);
  2036. else RETURN_IF_MATCH(RemoveSequence);
  2037. else RETURN_IF_MATCH(RenameSequence);
  2038. else RETURN_IF_MATCH(SetSequenceCyclic);
  2039. else RETURN_IF_MATCH(SetSequenceBlend);
  2040. else RETURN_IF_MATCH(SetSequencePriority);
  2041. else RETURN_IF_MATCH(SetSequenceGroundSpeed);
  2042. else RETURN_IF_MATCH(AddTrigger);
  2043. else RETURN_IF_MATCH(RemoveTrigger);
  2044. else return CmdInvalid;
  2045. #undef RETURN_IF_MATCH
  2046. }
  2047. void TSShapeConstructor::ChangeSet::write(TSShape* shape, Stream& stream, const String& savePath)
  2048. {
  2049. // First make a copy of the change-set
  2050. ChangeSet output;
  2051. for (S32 i = 0; i < mCommands.size(); i++)
  2052. output.add(mCommands[i]);
  2053. // Remove all __backup__ sequences (used during Shape Editing)
  2054. if (shape)
  2055. {
  2056. for (S32 i = 0; i < shape->sequences.size(); i++)
  2057. {
  2058. const char* seqName = shape->getName(shape->sequences[i].nameIndex);
  2059. if (dStrStartsWith(seqName, "__backup__"))
  2060. {
  2061. Command cmd("removeSequence");
  2062. cmd.addArgs(seqName);
  2063. output.add(cmd);
  2064. }
  2065. }
  2066. }
  2067. // Write the final change set to the stream
  2068. for (U32 i = 0; i < output.mCommands.size(); i++)
  2069. {
  2070. const Command& cmd = output.mCommands[i];
  2071. // Write the command
  2072. stream.writeTabs(1);
  2073. stream.writeText("%this.");
  2074. stream.writeText(cmd.name);
  2075. stream.writeText("(");
  2076. if (cmd.argc > 0)
  2077. {
  2078. // Use relative paths when possible
  2079. String str(cmd.argv[0]);
  2080. if (str.startsWith(savePath))
  2081. {
  2082. // Need to add "./" to a local file for the script file system. Otherwise
  2083. // it will be assumed to be a full and complete path when it comes to loading.
  2084. str = "./" + str.substr(savePath.length() + 1);
  2085. }
  2086. stream.writeText("\"");
  2087. stream.write(str.length(), str.c_str());
  2088. stream.writeText("\"");
  2089. // Write remaining arguments and newline
  2090. for (U32 j = 1; j < cmd.argc; j++)
  2091. {
  2092. // Use relative paths when possible
  2093. String relStr(cmd.argv[j]);
  2094. if (relStr.startsWith(savePath))
  2095. relStr = relStr.substr(savePath.length() + 1);
  2096. stream.writeText(", \"");
  2097. stream.write(relStr.length(), relStr.c_str());
  2098. stream.writeText("\"");
  2099. }
  2100. }
  2101. stream.writeText(");\r\n");
  2102. }
  2103. }
  2104. void TSShapeConstructor::ChangeSet::add( TSShapeConstructor::ChangeSet::Command& cmd )
  2105. {
  2106. // Lookup the command type
  2107. cmd.type = getCmdType(cmd.name); if (cmd.type == CmdInvalid)
  2108. return;
  2109. // Ignore operations on __proxy__ sequences (they are only used by the shape editor)
  2110. if (cmd.argv[0].startsWith("__proxy__") || ((cmd.type == CmdAddSequence) && cmd.argv[1].startsWith("__proxy__")))
  2111. return;
  2112. // Add the command to the change set (apply command specific collapsing)
  2113. bool addCommand = true;
  2114. switch (cmd.type)
  2115. {
  2116. // Node commands
  2117. case CmdSetNodeParent: addCommand = addCmd_setNodeParent(cmd); break;
  2118. case CmdSetNodeTransform: addCommand = addCmd_setNodeTransform(cmd); break;
  2119. case CmdRenameNode: addCommand = addCmd_renameNode(cmd); break;
  2120. case CmdRemoveNode: addCommand = addCmd_removeNode(cmd); break;
  2121. // Mesh commands
  2122. case CmdSetMeshSize: addCommand = addCmd_setMeshSize(cmd); break;
  2123. case CmdSetMeshType: addCommand = addCmd_setMeshType(cmd); break;
  2124. case CmdSetMeshMaterial: addCommand = addCmd_setMeshMaterial(cmd); break;
  2125. case CmdRemoveMesh: addCommand = addCmd_removeMesh(cmd); break;
  2126. // Object commands
  2127. case CmdSetObjectNode: addCommand = addCmd_setObjectNode(cmd); break;
  2128. case CmdRenameObject: addCommand = addCmd_renameObject(cmd); break;
  2129. case CmdRemoveObject: addCommand = addCmd_removeObject(cmd); break;
  2130. case CmdSetBounds: addCommand = addCmd_setBounds(cmd); break;
  2131. // Detail level commands
  2132. case CmdRenameDetailLevel: addCommand = addCmd_renameDetailLevel(cmd); break;
  2133. case CmdRemoveDetailLevel: addCommand = addCmd_removeDetailLevel(cmd); break;
  2134. case CmdSetDetailLevelSize: addCommand = addCmd_setDetailSize(cmd); break;
  2135. case CmdAddImposter: addCommand = addCmd_addImposter(cmd); break;
  2136. case CmdRemoveImposter: addCommand = addCmd_removeImposter(cmd); break;
  2137. // Sequence commands
  2138. case CmdAddSequence: addCommand = addCmd_addSequence(cmd); break;
  2139. case CmdSetSequencePriority: addCommand = addCmd_setSequencePriority(cmd); break;
  2140. case CmdSetSequenceGroundSpeed: addCommand = addCmd_setSequenceGroundSpeed(cmd); break;
  2141. case CmdSetSequenceCyclic: addCommand = addCmd_setSequenceCyclic(cmd); break;
  2142. case CmdSetSequenceBlend: addCommand = addCmd_setSequenceBlend(cmd); break;
  2143. case CmdRenameSequence: addCommand = addCmd_renameSequence(cmd); break;
  2144. case CmdRemoveSequence: addCommand = addCmd_removeSequence(cmd); break;
  2145. case CmdAddTrigger: addCommand = addCmd_addTrigger(cmd); break;
  2146. case CmdRemoveTrigger: addCommand = addCmd_removeTrigger(cmd); break;
  2147. // Other commands that do not have optimizations
  2148. default:
  2149. break;
  2150. }
  2151. if (addCommand)
  2152. mCommands.push_back(cmd);
  2153. }
  2154. //-----------------------------------------------------------------------------
  2155. // NODE COMMANDS
  2156. bool TSShapeConstructor::ChangeSet::addCmd_setNodeParent(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2157. {
  2158. // No dependencies, replace the parent argument for any previous addNode or
  2159. // setNodeParent.
  2160. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2161. {
  2162. Command& cmd = mCommands[index];
  2163. switch (cmd.type)
  2164. {
  2165. case CmdAddNode:
  2166. case CmdSetNodeParent:
  2167. if (namesEqual(cmd.argv[0], newCmd.argv[0]))
  2168. {
  2169. cmd.argv[1] = newCmd.argv[1]; // Replace parent argument
  2170. return false;
  2171. }
  2172. break;
  2173. default:
  2174. break;
  2175. }
  2176. }
  2177. return true;
  2178. }
  2179. bool TSShapeConstructor::ChangeSet::addCmd_setNodeTransform(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2180. {
  2181. // No dependencies, replace the parent argument for any previous addNode or
  2182. // setNodeParent.
  2183. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2184. {
  2185. Command& cmd = mCommands[index];
  2186. switch (cmd.type)
  2187. {
  2188. case CmdAddNode:
  2189. if (namesEqual(cmd.argv[0], newCmd.argv[0]))
  2190. {
  2191. cmd.argc = newCmd.argc + 1; // Replace transform argument
  2192. cmd.argv[2] = newCmd.argv[1];
  2193. cmd.argv[3] = newCmd.argv[2];
  2194. return false;
  2195. }
  2196. break;
  2197. case CmdSetNodeTransform:
  2198. if (namesEqual(cmd.argv[0], newCmd.argv[0]))
  2199. {
  2200. cmd = newCmd; // Collapse successive set transform commands
  2201. return false;
  2202. }
  2203. break;
  2204. default:
  2205. break;
  2206. }
  2207. }
  2208. return true;
  2209. }
  2210. bool TSShapeConstructor::ChangeSet::addCmd_renameNode(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2211. {
  2212. // Replace name argument for previous addNode or renameNode, but stop
  2213. // if the new name is already in use (can occur if 2 nodes switch names). eg.
  2214. // A->C
  2215. // B->A
  2216. // C->B (cannot replace the previous A->C with A->B as 'B' is in use)
  2217. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2218. {
  2219. Command& cmd = mCommands[index];
  2220. switch (cmd.type)
  2221. {
  2222. case CmdAddNode:
  2223. if (namesEqual(cmd.argv[0], newCmd.argv[0]))
  2224. {
  2225. cmd.argv[0] = newCmd.argv[1]; // Replace initial name argument
  2226. return false;
  2227. }
  2228. break;
  2229. case CmdRenameNode:
  2230. if (namesEqual(cmd.argv[1], newCmd.argv[0]))
  2231. {
  2232. cmd.argv[1] = newCmd.argv[1]; // Collapse successive renames
  2233. if (namesEqual(cmd.argv[0], cmd.argv[1]))
  2234. mCommands.erase(index); // Ignore empty renames
  2235. return false;
  2236. }
  2237. else if (namesEqual(cmd.argv[0], newCmd.argv[1]))
  2238. return true; // Name is in use, cannot go back further
  2239. break;
  2240. default:
  2241. break;
  2242. }
  2243. }
  2244. return true;
  2245. }
  2246. bool TSShapeConstructor::ChangeSet::addCmd_removeNode(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2247. {
  2248. // No dependencies. Remove any previous command that references the node
  2249. String nodeName(newCmd.argv[0]);
  2250. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2251. {
  2252. Command& cmd = mCommands[index];
  2253. switch (cmd.type)
  2254. {
  2255. case CmdAddNode:
  2256. if (namesEqual(cmd.argv[0], nodeName))
  2257. {
  2258. mCommands.erase(index); // Remove the added node
  2259. return false;
  2260. }
  2261. break;
  2262. case CmdSetNodeTransform:
  2263. case CmdSetNodeParent:
  2264. if (namesEqual(cmd.argv[0], nodeName))
  2265. mCommands.erase(index); // Remove any commands that reference the removed node
  2266. break;
  2267. case CmdRenameNode:
  2268. if (namesEqual(cmd.argv[1], nodeName))
  2269. {
  2270. nodeName = cmd.argv[0]; // Node is renamed
  2271. mCommands.erase(index);
  2272. }
  2273. break;
  2274. default:
  2275. break;
  2276. }
  2277. }
  2278. return true;
  2279. }
  2280. //-----------------------------------------------------------------------------
  2281. // SEQUENCE COMMANDS
  2282. bool TSShapeConstructor::ChangeSet::addCmd_addSequence(TSShapeConstructor::ChangeSet::Command& newCmd)
  2283. {
  2284. // For sequences added from ShapeEditor __backup sequences, search backwards for
  2285. // any changes made to the source of the __backup sequence. If none are found,
  2286. // use the __backup source instead of the __backup.
  2287. const char* backupPrefix = "__backup__";
  2288. if (!newCmd.argv[0].startsWith(backupPrefix))
  2289. return true;
  2290. S32 start = dStrlen(backupPrefix);
  2291. S32 end = newCmd.argv[0].find('_', 0, String::Right);
  2292. String sourceName = newCmd.argv[0].substr(start, end - start);
  2293. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2294. {
  2295. Command& cmd = mCommands[index];
  2296. switch (cmd.type)
  2297. {
  2298. case CmdSetSequencePriority:
  2299. case CmdSetSequenceCyclic:
  2300. case CmdSetSequenceBlend:
  2301. case CmdSetSequenceGroundSpeed:
  2302. // __backup sequence source has been modified => cannot go back further
  2303. if (namesEqual(cmd.argv[0], sourceName))
  2304. return true;
  2305. case CmdAddSequence:
  2306. if (namesEqual(cmd.argv[1], newCmd.argv[0]))
  2307. {
  2308. // No changes to the __backup sequence were found
  2309. newCmd.argv[0] = sourceName;
  2310. return true;
  2311. }
  2312. break;
  2313. default:
  2314. break;
  2315. }
  2316. }
  2317. return true;
  2318. }
  2319. bool TSShapeConstructor::ChangeSet::addCmd_setSequencePriority(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2320. {
  2321. // Replace any previous setSequencePriority command, but stop if the
  2322. // sequence is used as a source for addSequence (since the priority is
  2323. // copied).
  2324. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2325. {
  2326. Command& cmd = mCommands[index];
  2327. switch (cmd.type)
  2328. {
  2329. case CmdSetSequencePriority:
  2330. if (namesEqual(cmd.argv[0], newCmd.argv[0]))
  2331. {
  2332. cmd.argv[1] = newCmd.argv[1]; // Collapse successive set priority commands
  2333. return false;
  2334. }
  2335. break;
  2336. case CmdAddSequence:
  2337. if (namesEqual(cmd.argv[1], newCmd.argv[0]))
  2338. return true; // Sequence is used as source => cannot go back further
  2339. break;
  2340. default:
  2341. break;
  2342. }
  2343. }
  2344. return true;
  2345. }
  2346. bool TSShapeConstructor::ChangeSet::addCmd_setSequenceGroundSpeed(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2347. {
  2348. // Replace any previous setSequenceGroundSpeed command, but stop if the
  2349. // sequence is used as a source for addSequence (since the priority is
  2350. // copied).
  2351. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2352. {
  2353. Command& cmd = mCommands[index];
  2354. switch (cmd.type)
  2355. {
  2356. case CmdSetSequenceGroundSpeed:
  2357. if (namesEqual(cmd.argv[0], newCmd.argv[0]))
  2358. {
  2359. cmd.argv[1] = newCmd.argv[1]; // Collapse successive set ground speed commands
  2360. return false;
  2361. }
  2362. break;
  2363. case CmdAddSequence:
  2364. if (namesEqual(cmd.argv[1], newCmd.argv[0]))
  2365. return true; // Sequence is used as source => cannot go back further
  2366. break;
  2367. default:
  2368. break;
  2369. }
  2370. }
  2371. return true;
  2372. }
  2373. bool TSShapeConstructor::ChangeSet::addCmd_setSequenceCyclic(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2374. {
  2375. // Replace any previous setSequenceCyclic command, but stop if the
  2376. // sequence is used as a source for addSequence (since the priority is
  2377. // copied).
  2378. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2379. {
  2380. Command& cmd = mCommands[index];
  2381. switch (cmd.type)
  2382. {
  2383. case CmdSetSequenceCyclic:
  2384. if (namesEqual(cmd.argv[0], newCmd.argv[0]) &&
  2385. dAtob(cmd.argv[1]) != dAtob(newCmd.argv[1]))
  2386. {
  2387. mCommands.erase(index); // ignore both setCyclic commands (1 undoes the other)
  2388. return false;
  2389. }
  2390. break;
  2391. case CmdAddSequence:
  2392. if (namesEqual(cmd.argv[1], newCmd.argv[0]))
  2393. return true; // Sequence is used as source => cannot go back further
  2394. break;
  2395. default:
  2396. break;
  2397. }
  2398. }
  2399. return true;
  2400. }
  2401. bool TSShapeConstructor::ChangeSet::addCmd_setSequenceBlend(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2402. {
  2403. // Replace any previous setSequenceBlend command, but stop if the
  2404. // sequence is used as a source for addSequence (since the priority is
  2405. // copied).
  2406. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2407. {
  2408. Command& cmd = mCommands[index];
  2409. switch (cmd.type)
  2410. {
  2411. case CmdSetSequenceBlend:
  2412. if (namesEqual(cmd.argv[0], newCmd.argv[0]) &&
  2413. dAtob(cmd.argv[1]) != dAtob(newCmd.argv[1]) &&
  2414. namesEqual(cmd.argv[2], newCmd.argv[2]) &&
  2415. dAtoi(cmd.argv[3]) == dAtoi(newCmd.argv[3]))
  2416. {
  2417. mCommands.erase(index); // Ignore both setBlend commands (1 undoes the other)
  2418. return false;
  2419. }
  2420. break;
  2421. case CmdAddSequence:
  2422. if (namesEqual(cmd.argv[1], newCmd.argv[0]))
  2423. return true; // Sequence is used as source => cannot go back further
  2424. break;
  2425. default:
  2426. break;
  2427. }
  2428. }
  2429. return true;
  2430. }
  2431. bool TSShapeConstructor::ChangeSet::addCmd_renameSequence(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2432. {
  2433. // Replace name argument for previous addSequence or renameSequence, but stop
  2434. // if the new name is already in use (can occur if 2 nodes switch names). eg.
  2435. // A->C
  2436. // B->A
  2437. // C->B (cannot replace the previous A->C with A->B as 'B' is in use)
  2438. //
  2439. // Once a previous command is found, go forward through the command list and
  2440. // update any references to the old name
  2441. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2442. {
  2443. Command& cmd = mCommands[index];
  2444. switch (cmd.type)
  2445. {
  2446. case CmdRenameSequence:
  2447. if (namesEqual(cmd.argv[0], newCmd.argv[1]) && !namesEqual(cmd.argv[1], newCmd.argv[0]))
  2448. return true; // Name is in use => cannot go back further
  2449. // fall through to common processing
  2450. case CmdAddSequence:
  2451. if (namesEqual(cmd.argv[1], newCmd.argv[0]))
  2452. {
  2453. if (cmd.type == CmdRenameSequence)
  2454. {
  2455. cmd.argv[1] = newCmd.argv[1]; // Collapse successive renames
  2456. if (namesEqual(cmd.argv[0], cmd.argv[1]))
  2457. mCommands.erase(index); // Ignore empty renames
  2458. }
  2459. else if (cmd.type == CmdAddSequence)
  2460. {
  2461. cmd.argv[1] = newCmd.argv[1]; // Replace initial name argument
  2462. }
  2463. // Update any references to the old name
  2464. for (S32 j = index + 1; j < mCommands.size(); j++)
  2465. {
  2466. Command& cmd2 = mCommands[j];
  2467. switch (cmd2.type)
  2468. {
  2469. case CmdSetSequencePriority:
  2470. case CmdSetSequenceCyclic:
  2471. case CmdSetSequenceBlend:
  2472. case CmdSetSequenceGroundSpeed:
  2473. if (namesEqual(cmd2.argv[0], newCmd.argv[0]))
  2474. cmd2.argv[0] = newCmd.argv[1];
  2475. break;
  2476. }
  2477. }
  2478. return false;
  2479. }
  2480. break;
  2481. default:
  2482. break;
  2483. }
  2484. }
  2485. return true;
  2486. }
  2487. bool TSShapeConstructor::ChangeSet::addCmd_removeSequence(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2488. {
  2489. // Remove any previous command that references the sequence, but stop if the
  2490. // sequence is used as a source for addSequence
  2491. String seqName(newCmd.argv[0]);
  2492. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2493. {
  2494. Command& cmd = mCommands[index];
  2495. switch (cmd.type)
  2496. {
  2497. case CmdAddSequence:
  2498. if (namesEqual(cmd.argv[1], seqName))
  2499. {
  2500. mCommands.erase(index); // Remove the added sequence
  2501. return false;
  2502. }
  2503. else if (namesEqual(cmd.argv[0], seqName))
  2504. {
  2505. // Removed sequence is used as source for another sequence => can't
  2506. // go back any further
  2507. return true;
  2508. }
  2509. break;
  2510. case CmdRenameSequence:
  2511. if (namesEqual(cmd.argv[1], seqName))
  2512. {
  2513. seqName = cmd.argv[0]; // Sequence is renamed
  2514. mCommands.erase(index);
  2515. }
  2516. break;
  2517. case CmdSetSequencePriority:
  2518. case CmdSetSequenceGroundSpeed:
  2519. case CmdSetSequenceCyclic:
  2520. case CmdSetSequenceBlend:
  2521. case CmdAddTrigger:
  2522. case CmdRemoveTrigger:
  2523. if (namesEqual(cmd.argv[0], seqName))
  2524. mCommands.erase(index); // Remove any commands that reference the removed sequence
  2525. break;
  2526. default:
  2527. break;
  2528. }
  2529. }
  2530. return true;
  2531. }
  2532. bool TSShapeConstructor::ChangeSet::addCmd_addTrigger(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2533. {
  2534. // Remove a matching removeTrigger command, but stop if the sequence is used as
  2535. // a source for addSequence (since triggers are copied).
  2536. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2537. {
  2538. Command& cmd = mCommands[index];
  2539. switch (cmd.type)
  2540. {
  2541. case CmdRemoveTrigger:
  2542. if (namesEqual(cmd.argv[0], newCmd.argv[0]) &&
  2543. cmd.argv[1] == newCmd.argv[1] &&
  2544. cmd.argv[2] == newCmd.argv[2])
  2545. {
  2546. mCommands.erase(index); // Remove previous removeTrigger command
  2547. return false;
  2548. }
  2549. break;
  2550. case CmdAddSequence:
  2551. if (namesEqual(cmd.argv[1], newCmd.argv[0]))
  2552. return true; // Sequence is used as a source => cannot go back further
  2553. break;
  2554. default:
  2555. break;
  2556. }
  2557. }
  2558. return true;
  2559. }
  2560. bool TSShapeConstructor::ChangeSet::addCmd_removeTrigger(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2561. {
  2562. // Remove a matching addTrigger command, but stop if the sequence is used as
  2563. // a source for addSequence (since triggers are copied).
  2564. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2565. {
  2566. Command& cmd = mCommands[index];
  2567. switch (cmd.type)
  2568. {
  2569. case CmdAddTrigger:
  2570. if (namesEqual(cmd.argv[0], newCmd.argv[0]) &&
  2571. cmd.argv[1] == newCmd.argv[1] &&
  2572. cmd.argv[2] == newCmd.argv[2])
  2573. {
  2574. mCommands.erase(index); // Remove previous addTrigger command
  2575. return false;
  2576. }
  2577. break;
  2578. case CmdAddSequence:
  2579. if (namesEqual(cmd.argv[1], newCmd.argv[0]))
  2580. return true; // Sequence is used as a source => cannot go back further
  2581. break;
  2582. default:
  2583. break;
  2584. }
  2585. }
  2586. return true;
  2587. }
  2588. //-----------------------------------------------------------------------------
  2589. // MESH COMMANDS
  2590. bool TSShapeConstructor::ChangeSet::addCmd_setMeshSize(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2591. {
  2592. // Replace size argument for previous addMesh or setMeshSize, but stop if the
  2593. // new name is already in use (can occur if 2 nodes switch names). eg.
  2594. // A->C
  2595. // B->A
  2596. // C->B (cannot replace the previous A->C with A->B as 'B' is in use)
  2597. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2598. {
  2599. Command& cmd = mCommands[index];
  2600. switch (cmd.type)
  2601. {
  2602. case CmdAddMesh:
  2603. if (namesEqual(cmd.argv[0], newCmd.argv[0]))
  2604. {
  2605. cmd.argv[0] = newCmd.argv[1]; // Replace initial size argument
  2606. return false;
  2607. }
  2608. break;
  2609. case CmdSetMeshSize:
  2610. if (cmd.argv[1] == newCmd.argv[0])
  2611. {
  2612. cmd.argv[1] = newCmd.argv[1]; // Collapse successive size sets
  2613. if (cmd.argv[0] == cmd.argv[1])
  2614. mCommands.erase(index); // Ignore empty resizes
  2615. return false;
  2616. }
  2617. else if (cmd.argv[0] == newCmd.argv[1])
  2618. return true; // Size is in use, cannot go back further
  2619. break;
  2620. default:
  2621. break;
  2622. }
  2623. }
  2624. return true;
  2625. }
  2626. bool TSShapeConstructor::ChangeSet::addCmd_setMeshType(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2627. {
  2628. // Replace any previous setMeshType command, but stop if the mesh is used as
  2629. // a source for addMesh (since the type is copied).
  2630. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2631. {
  2632. Command& cmd = mCommands[index];
  2633. switch (cmd.type)
  2634. {
  2635. case CmdSetMeshType:
  2636. if (namesEqual(cmd.argv[0], newCmd.argv[0]))
  2637. {
  2638. cmd.argv[1] = newCmd.argv[1]; // Collapse successive set type commands
  2639. return false;
  2640. }
  2641. break;
  2642. case CmdAddMesh:
  2643. if (namesEqual(cmd.argv[0], newCmd.argv[0]))
  2644. return true; // Mesh is used as source => cannot go back further
  2645. break;
  2646. default:
  2647. break;
  2648. }
  2649. }
  2650. return true;
  2651. }
  2652. bool TSShapeConstructor::ChangeSet::addCmd_setMeshMaterial(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2653. {
  2654. // Replace any previous setMeshMaterial command, but stop if the mesh is used as
  2655. // a source for addMesh (since the materials are copied).
  2656. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2657. {
  2658. Command& cmd = mCommands[index];
  2659. switch (cmd.type)
  2660. {
  2661. case CmdSetMeshMaterial:
  2662. if (namesEqual(cmd.argv[0], newCmd.argv[0]))
  2663. {
  2664. cmd.argv[1] = newCmd.argv[1]; // Collapse successive set material commands
  2665. return false;
  2666. }
  2667. break;
  2668. case CmdAddMesh:
  2669. if (namesEqual(cmd.argv[0], newCmd.argv[0]))
  2670. return true; // Mesh is used as source => cannot go back further
  2671. break;
  2672. default:
  2673. break;
  2674. }
  2675. }
  2676. return true;
  2677. }
  2678. bool TSShapeConstructor::ChangeSet::addCmd_removeMesh(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2679. {
  2680. // Remove any previous command that references the mesh, but stop if the mesh
  2681. // is used as a source for addMesh
  2682. String meshName(newCmd.argv[0]);
  2683. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2684. {
  2685. Command& cmd = mCommands[index];
  2686. switch (cmd.type)
  2687. {
  2688. case CmdAddMesh:
  2689. if (namesEqual(cmd.argv[0], meshName))
  2690. {
  2691. mCommands.erase(index); // Remove the added mesh
  2692. return false;
  2693. }
  2694. else if (namesEqual(cmd.argv[2], meshName))
  2695. {
  2696. // Removed mesh is used as source for another mesh => can't go back
  2697. // any further
  2698. return true;
  2699. }
  2700. break;
  2701. case CmdAddPrimitive:
  2702. if (namesEqual(cmd.argv[0], meshName))
  2703. {
  2704. mCommands.erase(index); // Remove the added primitive
  2705. return false;
  2706. }
  2707. break;
  2708. case CmdSetMeshSize:
  2709. case CmdSetMeshType:
  2710. case CmdSetMeshMaterial:
  2711. if (namesEqual(cmd.argv[0], meshName))
  2712. mCommands.erase(index); // Remove any commands that reference the removed mesh
  2713. break;
  2714. default:
  2715. break;
  2716. }
  2717. }
  2718. return true;
  2719. }
  2720. //-----------------------------------------------------------------------------
  2721. // OBJECT COMMANDS
  2722. bool TSShapeConstructor::ChangeSet::addCmd_setObjectNode(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2723. {
  2724. // No dependencies, replace the node argument for any previous parent argument for any previous addNode or
  2725. // setNodeParent.
  2726. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2727. {
  2728. Command& cmd = mCommands[index];
  2729. switch (cmd.type)
  2730. {
  2731. case CmdAddMesh:
  2732. {
  2733. S32 dummy;
  2734. if (namesEqual(String::GetTrailingNumber(cmd.argv[0], dummy), newCmd.argv[0]))
  2735. {
  2736. cmd.argv[3] = newCmd.argv[1]; // Replace node argument
  2737. return false;
  2738. }
  2739. break;
  2740. }
  2741. case CmdSetObjectNode:
  2742. if (namesEqual(cmd.argv[0], newCmd.argv[0]))
  2743. {
  2744. cmd.argv[1] = newCmd.argv[1];
  2745. return false;
  2746. }
  2747. break;
  2748. default:
  2749. break;
  2750. }
  2751. }
  2752. return true;
  2753. }
  2754. bool TSShapeConstructor::ChangeSet::addCmd_renameObject(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2755. {
  2756. // Replace name argument for previous renameObject, but stop if the new name
  2757. // is already in use (can occur if 2 objects switch names). eg.
  2758. // A->C
  2759. // B->A
  2760. // C->B (cannot replace the previous A->C with A->B as 'B' is in use)
  2761. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2762. {
  2763. Command& cmd = mCommands[index];
  2764. switch (cmd.type)
  2765. {
  2766. case CmdRenameObject:
  2767. if (namesEqual(cmd.argv[1], newCmd.argv[0]))
  2768. {
  2769. cmd.argv[1] = newCmd.argv[1]; // Collapse successive renames
  2770. if (namesEqual(cmd.argv[0], cmd.argv[1]))
  2771. mCommands.erase(index); // Ignore empty renames
  2772. return false;
  2773. }
  2774. else if (namesEqual(cmd.argv[0], newCmd.argv[1]))
  2775. return true; // Name is in use, cannot go back further
  2776. break;
  2777. default:
  2778. break;
  2779. }
  2780. }
  2781. return true;
  2782. }
  2783. bool TSShapeConstructor::ChangeSet::addCmd_removeObject(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2784. {
  2785. // Remove any previous command that references the object, but stop if any
  2786. // object mesh is used as a source for addMesh
  2787. S32 dummy;
  2788. String objName(newCmd.argv[0]);
  2789. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2790. {
  2791. Command& cmd = mCommands[index];
  2792. switch (cmd.type)
  2793. {
  2794. case CmdAddMesh:
  2795. if (namesEqual(String::GetTrailingNumber(cmd.argv[0], dummy), objName))
  2796. {
  2797. mCommands.erase(index); // Remove the added mesh
  2798. // Must still add the removeObject command as there could be multiple
  2799. // meshes in the object
  2800. }
  2801. else if (namesEqual(String::GetTrailingNumber(cmd.argv[2], dummy), objName))
  2802. {
  2803. // Removed mesh is used as source for another mesh => can't go back
  2804. // any further
  2805. return true;
  2806. }
  2807. break;
  2808. case CmdRenameObject:
  2809. if (namesEqual(cmd.argv[1], objName))
  2810. {
  2811. objName = cmd.argv[0]; // Object is renamed
  2812. mCommands.erase(index);
  2813. }
  2814. break;
  2815. case CmdSetObjectNode:
  2816. if (namesEqual(cmd.argv[0], objName))
  2817. mCommands.erase(index); // Remove any commands that reference the removed object
  2818. break;
  2819. case CmdSetMeshSize:
  2820. case CmdSetMeshType:
  2821. case CmdSetMeshMaterial:
  2822. case CmdRemoveMesh:
  2823. if (namesEqual(String::GetTrailingNumber(cmd.argv[0], dummy), objName))
  2824. mCommands.erase(index); // Remove comands that reference the removed object
  2825. break;
  2826. default:
  2827. break;
  2828. }
  2829. }
  2830. return true;
  2831. }
  2832. bool TSShapeConstructor::ChangeSet::addCmd_setBounds(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2833. {
  2834. // Only the last bounds update applies, so replace any previous command.
  2835. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2836. {
  2837. Command& cmd = mCommands[index];
  2838. switch (cmd.type)
  2839. {
  2840. case CmdSetBounds:
  2841. mCommands.erase(index);
  2842. break;
  2843. default:
  2844. break;
  2845. }
  2846. }
  2847. return true;
  2848. }
  2849. //-----------------------------------------------------------------------------
  2850. // DETAIL COMMANDS
  2851. bool TSShapeConstructor::ChangeSet::addCmd_renameDetailLevel(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2852. {
  2853. // Replace name argument for previous renameDetailLevel, but stop if the new
  2854. // name is already in use (can occur if 2 objects switch names). eg.
  2855. // A->C
  2856. // B->A
  2857. // C->B (cannot replace the previous A->C with A->B as 'B' is in use)
  2858. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2859. {
  2860. Command& cmd = mCommands[index];
  2861. switch (cmd.type)
  2862. {
  2863. case CmdRenameDetailLevel:
  2864. if (namesEqual(cmd.argv[1], newCmd.argv[0]))
  2865. {
  2866. cmd.argv[1] = newCmd.argv[1]; // Collapse successive renames
  2867. if (namesEqual(cmd.argv[0], cmd.argv[1]))
  2868. mCommands.erase(index); // Ignore empty renames
  2869. return false;
  2870. }
  2871. else if (namesEqual(cmd.argv[0], newCmd.argv[1]))
  2872. return true; // Name is in use, cannot go back further
  2873. break;
  2874. default:
  2875. break;
  2876. }
  2877. }
  2878. return true;
  2879. }
  2880. bool TSShapeConstructor::ChangeSet::addCmd_removeDetailLevel(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2881. {
  2882. // Remove any previous command that references the detail, but stop if a mesh
  2883. // is used as a source for addMesh
  2884. S32 detSize = dAtoi(newCmd.argv[0]);
  2885. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2886. {
  2887. Command& cmd = mCommands[index];
  2888. S32 size;
  2889. switch (cmd.type)
  2890. {
  2891. case CmdAddMesh:
  2892. String::GetTrailingNumber(cmd.argv[2], size);
  2893. if (size == detSize)
  2894. {
  2895. // Removed detail is used as source for another mesh => can't go back
  2896. // any further
  2897. return true;
  2898. }
  2899. // fall through
  2900. case CmdAddPrimitive:
  2901. case CmdSetMeshSize:
  2902. case CmdSetMeshType:
  2903. case CmdSetMeshMaterial:
  2904. case CmdRemoveMesh:
  2905. String::GetTrailingNumber(cmd.argv[0], size);
  2906. if (size == detSize)
  2907. mCommands.erase(index);
  2908. break;
  2909. case CmdAddImposter:
  2910. case CmdAddCollisionDetail:
  2911. if (dAtoi(cmd.argv[0]) == detSize)
  2912. {
  2913. mCommands.erase(index);
  2914. return false;
  2915. }
  2916. break;
  2917. default:
  2918. break;
  2919. }
  2920. }
  2921. return true;
  2922. }
  2923. bool TSShapeConstructor::ChangeSet::addCmd_setDetailSize(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2924. {
  2925. // Similar to renameXXX. Replace size argument for previous addImposter or
  2926. // setDetailLevelSize, but stop if the new size is already in use (can occur
  2927. // if 2 details switch sizes). eg.
  2928. // A->C
  2929. // B->A
  2930. // C->B (cannot replace the previous A->C with A->B as 'B' is in use)
  2931. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2932. {
  2933. Command& cmd = mCommands[index];
  2934. switch (cmd.type)
  2935. {
  2936. case CmdAddImposter:
  2937. if (cmd.argv[0] == newCmd.argv[0])
  2938. {
  2939. cmd.argv[0] = newCmd.argv[1]; // Change detail size argument
  2940. return false;
  2941. }
  2942. break;
  2943. case CmdSetDetailLevelSize:
  2944. if (cmd.argv[1] == newCmd.argv[0])
  2945. {
  2946. cmd.argv[1] = newCmd.argv[1]; // Collapse successive detail size changes
  2947. if (cmd.argv[0] == cmd.argv[1])
  2948. mCommands.erase(index); // Ignore empty changes
  2949. return false;
  2950. }
  2951. else if (cmd.argv[0] == newCmd.argv[1])
  2952. return true; // Detail size already in use => cannot go back further
  2953. break;
  2954. default:
  2955. break;
  2956. }
  2957. }
  2958. return true;
  2959. }
  2960. bool TSShapeConstructor::ChangeSet::addCmd_addImposter(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2961. {
  2962. // Remove previous removeImposter, and replace any previous addImposter. If
  2963. // replacing, also remove any setDetailLevelSize for the old imposter
  2964. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  2965. {
  2966. Command& cmd = mCommands[index];
  2967. switch (cmd.type)
  2968. {
  2969. case CmdAddImposter:
  2970. // Replace the AddImposter command, but first remove any reference to
  2971. // the added detail level.
  2972. for (S32 j = index + 1; j < mCommands.size(); j++)
  2973. {
  2974. Command& cmd2 = mCommands[j];
  2975. if ((cmd2.type == CmdSetDetailLevelSize) &&
  2976. cmd2.argv[0] == cmd.argv[0])
  2977. {
  2978. mCommands.erase(j);
  2979. break;
  2980. }
  2981. }
  2982. // Replace previous addImposter command
  2983. cmd = newCmd;
  2984. return false;
  2985. case CmdRemoveImposter:
  2986. mCommands.erase(index); // Remove previous removeImposter command
  2987. break;
  2988. default:
  2989. break;
  2990. }
  2991. }
  2992. return true;
  2993. }
  2994. bool TSShapeConstructor::ChangeSet::addCmd_removeImposter(const TSShapeConstructor::ChangeSet::Command& newCmd)
  2995. {
  2996. // Remove any previous addImposter, and also remove any setDetailLevelSize
  2997. // for that imposter.
  2998. // Always need to return true, since we could be removing imposters already
  2999. // present in the shape (not added with addImposter).
  3000. for (S32 index = mCommands.size() - 1; index >= 0; index--)
  3001. {
  3002. Command& cmd = mCommands[index];
  3003. switch (cmd.type)
  3004. {
  3005. case CmdAddImposter:
  3006. // Remove the AddImposter command, but first remove any reference to
  3007. // the added detail level.
  3008. for (S32 j = index + 1; j < mCommands.size(); j++)
  3009. {
  3010. Command& cmd2 = mCommands[j];
  3011. if ((cmd2.type == CmdSetDetailLevelSize) &&
  3012. cmd2.argv[0] == cmd.argv[0])
  3013. {
  3014. mCommands.erase(j);
  3015. break;
  3016. }
  3017. }
  3018. mCommands.erase(index);
  3019. break;
  3020. default:
  3021. break;
  3022. }
  3023. }
  3024. return true;
  3025. }
  3026. void TSShapeConstructor::onActionPerformed()
  3027. {
  3028. // Reinit shape if we modify stuff in the shape editor, otherwise delay
  3029. if (!mLoadingShape)
  3030. {
  3031. if (mShape && mShape->needsReinit())
  3032. {
  3033. mShape->init();
  3034. }
  3035. }
  3036. }