tsShapeConstruct.cpp 124 KB

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