Import.cpp 74 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404
  1. /******************************************************************************/
  2. #include "stdafx.h"
  3. /******************************************************************************/
  4. ImporterClass Importer;
  5. /******************************************************************************/
  6. /******************************************************************************/
  7. void ImporterClass::Import::MaterialEx::copyTo(EditMaterial &dest, C TimeStamp &time)C
  8. {
  9. dest.create(mtrl, time); // set from 'Material' instead of 'XMaterial' because it had '_adjustParams' called
  10. dest. flip_normal_y= flip_normal_y; dest. flip_normal_y_time=time;
  11. dest. color_map= color_map; dest. color_map_time=time;
  12. dest. alpha_map= alpha_map; dest. alpha_map_time=time;
  13. dest. bump_map= bump_map; dest. bump_map_time=time;
  14. dest. normal_map= normal_map; dest. normal_map_time=time;
  15. dest. specular_map= specular_map; dest. specular_map_time=time;
  16. dest. glow_map= glow_map; dest. glow_map_time=time;
  17. dest. light_map= light_map; dest. light_map_time=time;
  18. dest.reflection_map= reflection_map; dest.reflection_map_time=time;
  19. dest. macro_map= S; dest. macro_map_time=time;
  20. dest.detail_color = detail_color_map; dest. detail_map_time=time;
  21. dest.detail_bump = detail_bump_map;
  22. dest.detail_normal =detail_normal_map;
  23. dest. base_0_tex= base_0_id;
  24. dest. base_1_tex= base_1_id;
  25. dest. detail_tex= detail_id;
  26. dest. macro_tex= macro_id;
  27. dest.reflection_tex=reflection_id;
  28. dest. light_tex= light_id;
  29. }
  30. void ImporterClass::Import::MaterialEx::check(C Str &path, Str &tex)
  31. {
  32. Mems<Edit::FileParams> texs=Edit::FileParams::Decode(tex);
  33. FREPA(texs)if(!FExistSystem(texs[i].name))
  34. {
  35. Str test=path; test.tailSlash(true)+=GetBaseNoExt(texs[i].name); test+=".webp";
  36. if(FExistSystem(test))texs[i].name=test;
  37. }
  38. tex=Edit::FileParams::Encode(texs);
  39. }
  40. void ImporterClass::Import::MaterialEx::process(C Str &path)
  41. {
  42. copyParamsTo(mtrl);
  43. if(path.is())
  44. {
  45. check(path, color_map);
  46. check(path, alpha_map);
  47. check(path, bump_map);
  48. check(path, glow_map);
  49. check(path, light_map);
  50. check(path, normal_map);
  51. check(path, specular_map);
  52. check(path, reflection_map);
  53. }
  54. if(GetExt(color_map)=="img" || GetExt(normal_map)=="img" || GetExt(detail_color_map)=="img" || GetExt(reflection_map)=="img") // this is 'EE.Material' ("mtrl" format)
  55. {
  56. Str b0=color_map, b1=normal_map, d=detail_color_map, r=reflection_map, l=light_map, m;
  57. base_0 .load(b0); ImageProps(base_0 , & base_0_id, null, ForceHQMtrlBase0 ? FORCE_HQ : 0);
  58. base_1 .load(b1); ImageProps(base_1 , & base_1_id, null, ForceHQMtrlBase1 ? FORCE_HQ : 0);
  59. detail .load(d ); ImageProps(detail , & detail_id, null, IGNORE_ALPHA);
  60. macro .load(m ); ImageProps(macro , & macro_id, null, IGNORE_ALPHA);
  61. reflection.load(r ); ImageProps(reflection, &reflection_id, null, IGNORE_ALPHA);
  62. light .load(l ); ImageProps(light , & light_id, null, IGNORE_ALPHA);
  63. Edit::FileParams fp;
  64. fp=b0; if(fp.name.is())fp.getParam("channel").setValue("rgb"); color_map=fp.encode();
  65. alpha_map.clear();
  66. bump_map.clear();
  67. glow_map.clear();
  68. normal_map.clear();
  69. specular_map.clear();
  70. fp=d; if(fp.name.is())fp.getParam("channel").setValue("z" ); detail_color_map =fp.encode();
  71. fp=d; if(fp.name.is())fp.getParam("channel").setValue("w" ); detail_bump_map =fp.encode();
  72. fp=d; if(fp.name.is())fp.getParam("channel").setValue("xy"); detail_normal_map=fp.encode();
  73. if(b1.is())
  74. {
  75. fp=b0; if(fp.name.is())fp.getParam("channel").setValue("a" ); bump_map=fp.encode();
  76. fp=b1; fp.getParam("channel").setValue("xy"); normal_map=fp.encode();
  77. fp=b1; fp.getParam("channel").setValue("z" ); specular_map=fp.encode();
  78. fp=b1; fp.getParam("channel").setValue("w" ); alpha_map=fp.encode();
  79. fp=b1; fp.getParam("channel").setValue("w" ); glow_map=fp.encode();
  80. }else
  81. if(b0.is())
  82. {
  83. fp=b0; fp.getParam("channel").setValue("a"); alpha_map=fp.encode();
  84. }
  85. }else
  86. {
  87. Image col, alpha, bump, normal, spec, glow;
  88. ImportImage( col, color_map);
  89. ImportImage( alpha, alpha_map);
  90. ImportImage( bump, bump_map);
  91. ImportImage( normal, normal_map);
  92. ImportImage( spec, specular_map);
  93. ImportImage( glow, glow_map);
  94. ImportImage(reflection, reflection_map);
  95. ImportImage( light, light_map);
  96. // process textures only if they're added for the first time, otherwise delete them so they won't be saved
  97. uint bt=CreateBaseTextures(base_0, base_1, col, alpha, bump, normal, spec, glow, true, flip_normal_y);
  98. IMAGE_TYPE ct; ImageProps( base_0, & base_0_id, &ct, ForceHQMtrlBase0 ? FORCE_HQ : 0); if(Importer.includeTex( base_0_id)) base_0 .copyTry(base_0 , -1, -1, -1, ct, IMAGE_2D, 0, FILTER_BEST, false, false, false, false); else base_0 .del();
  99. ImageProps( base_1, & base_1_id, &ct, ForceHQMtrlBase1 ? FORCE_HQ : 0); if(Importer.includeTex( base_1_id)) base_1 .copyTry(base_1 , -1, -1, -1, ct, IMAGE_2D, 0, FILTER_BEST, false, false, false, true ); else base_1 .del();
  100. ImageProps(reflection, &reflection_id, &ct, IGNORE_ALPHA ); if(Importer.includeTex(reflection_id)){FixAlpha(reflection, ct); reflection.copyTry(reflection, -1, -1, -1, ct, IMAGE_CUBE );}else reflection.del();
  101. ImageProps( light, & light_id, &ct, IGNORE_ALPHA ); if(Importer.includeTex( light_id)){FixAlpha(light , ct); light .copyTry(light , -1, -1, -1, ct, IMAGE_2D, 0 );}else light .del();
  102. mtrl._adjustParams(~bt, bt);
  103. }
  104. }
  105. Str ImporterClass::Import::nodeName(int i)C {return InRange(i, bone_names) ? bone_names[i] : InRange(i, skel.bones) ? (Str)skel.bones[i].name : S;}
  106. Str ImporterClass::Import::nodeUID(int i)C // unique string identifying a node !! needs to be the same as 'EditSkeleton.nodeUID' !!
  107. {
  108. Str path; for(; InRange(i, skel.bones); )
  109. {
  110. Str node_name=nodeName(i);
  111. path+=node_name; // node name
  112. int parent=skel.boneParent(i), child_index=0; REPD(j, i)if(skel.boneParent(j)==parent && nodeName(j)==node_name)child_index++; if(child_index){path+=CharAlpha; path+=child_index;} // node child index in parent (only children with same names are counted)
  113. path+='/'; // separator
  114. i=parent;
  115. }
  116. return path;
  117. }
  118. ::ImporterClass::Import& ImporterClass::Import::set(C UID &elm_id, C UID &parent_id, C Str &file, MODE mode, ELM_TYPE type, C Str &force_name, bool remember_result)
  119. {
  120. T.elm_id=elm_id; T.parent_id=parent_id; T.file=file; T.mode=mode; T.type=type; T.force_name=force_name; T.remember_result=remember_result;
  121. switch(type)
  122. {
  123. case ELM_FONT: edit_font.load(Proj.editPath(elm_id)); break;
  124. case ELM_IMAGE: // check for skybox
  125. {
  126. Mems<Edit::FileParams> files=Edit::FileParams::Decode(T.file);
  127. if(files.elms()==6)
  128. {
  129. // load helper data from the project
  130. File src;
  131. UID image_id;
  132. REPA(files)if(DecodeFileName(files[i].name, image_id))if(src.readTry(Proj.editPath(image_id)))
  133. {
  134. ImageEx &image=images(i);
  135. src.copy(image.raw.writeMem());
  136. if(Elm *elm=Proj.findElm(image_id))if(ElmImage *data=elm->imageData())image.cube=(data->mode==IMAGE_CUBE);
  137. }
  138. // check if any of the images were not imported
  139. if(src.readTry(Proj.editPath(elm_id)))src.copy(raw.writeMem());
  140. }
  141. }break;
  142. case ELM_OBJ: // check if we want to ignore animations
  143. {
  144. if(mode==UPDATE // check only for UPDATE because others treat 'elm_id' as target
  145. && Proj.objToSkel(elm_id).valid())ignore_anims=true; // if object that's being reloaded already had a skeleton set, then ignore loading animations because they were already imported before
  146. }break;
  147. }
  148. return T;
  149. }
  150. bool ImporterClass::Import::ApplyVolume(ptr data, int size, flt vol)
  151. {
  152. if(data)
  153. {
  154. if(size&1)return false; // need 2 byte alignment for a full sample
  155. short *sample=(short*)data;
  156. REP(size/SIZE(short))
  157. {
  158. *sample=Mid(Round(*sample * vol), SHORT_MIN, SHORT_MAX);
  159. sample++;
  160. }
  161. }
  162. return true;
  163. }
  164. bool ImporterClass::Import::import() // !! this is called on a secondary thread !!
  165. {
  166. Mems<Edit::FileParams> files=Edit::FileParams::Decode(T.file);
  167. Str file; if(files.elms())file=files[0].name;
  168. bool all_nodes_as_bones=(type==ELM_ANIM || mode==ANIM); // when importing animations we have to treat all nodes as potential bones, because it's possible that asset developer used helper/dummies to animate nodes which in the base object mesh model got imported as bones due to mesh skinning, however animations don't need the mesh and without the mesh and thus skinning, the nodes would not get detected as bones, to workaround this we force all nodes as bones, and later we just remove them depending if they're present in the already existing object mesh skeleton from before, removing happens in 'removeExtraBones' which is called in processing the animation later.
  169. // import
  170. switch(type)
  171. {
  172. case ELM_OBJ:
  173. {
  174. MemPtr<XAnimation> anims; if(mode!=CLOTH && mode!=ADD && !ignore_anims)anims.point(T.anims);
  175. MemPtr<XMaterial > mtrls; if(mode!=ANIM )mtrls.point(T.mtrls);
  176. if(EE::Import(file, (mode!=ANIM) ? &mesh : null, &skel, anims, mtrls, part_mtrl_index, bone_names, all_nodes_as_bones))
  177. {
  178. FixMesh(mesh);
  179. REPAO(anims).anim.linear(anims[i].fps>=LinearAnimFpsLimit).clip(0, anims[i].anim.length()); // set linear mode and remove any keyframes outside of anim range
  180. Str path=GetPath(file); FREPAO(T.mtrls).process(path);
  181. return true;
  182. }
  183. }break;
  184. case ELM_SOUND:
  185. {
  186. SOUND_CODEC codec=SOUND_NONE; if(C TextParam *param=files[0].findParam("codec" ))codec =TextSoundCodec(param->value);
  187. dbl start=0; if(C TextParam *param=files[0].findParam("start" ))start =param->asDbl();
  188. dbl end =-1; if(C TextParam *param=files[0].findParam("end" ))end =param->asDbl();
  189. dbl length=-1; if(C TextParam *param=files[0].findParam("length"))length=param->asDbl();
  190. flt volume=1; if(C TextParam *param=files[0].findParam("volume"))volume=param->asFlt(); bool apply_vol=!Equal(volume, 1);
  191. int hz=-1; if(C TextParam *param=files[0].findParam("hz" ))hz =param->asInt();
  192. int rel_bit_rate=-1 ; C TextParam *rbr=files[0].findParam("relBitRate"); if(!rbr)rbr=files[0].findParam("relativeBitRate"); if(rbr)rel_bit_rate=rbr->asInt();
  193. if(codec || rel_bit_rate>=0 || start>0 || end>=0 || length>=0 || hz>0 || apply_vol)
  194. {
  195. SoundStream s; if(s.create(file) && s.block())
  196. {
  197. long start_sample=((start> 0) ? RoundL(start*s.frequency()) : 0); MIN(start_sample, s.samples());
  198. long end_sample=((end >=0) ? RoundL(end *s.frequency()) : s.samples()); MIN( end_sample, s.samples());
  199. long length_sample=Max(0, end_sample-start_sample); if(length>=0)MIN(length_sample, RoundL(length*s.frequency()));
  200. if(!codec)codec=s.codec(); // if not specified then use original
  201. if(hz<=0)hz=s.frequency();
  202. int bit_rate=-1; if(rel_bit_rate>0) // calculate from relative bit rate
  203. {
  204. bit_rate=long(rel_bit_rate*1000) * s.frequency()/44100 * s.channels()/2;
  205. }else
  206. if(rel_bit_rate<0 && (Equal(s.codecName(), "opus") || Equal(s.codecName(), "vorbis"))) // get from source (accept only from good lossy compressed codecs, because Raw/Flac will have very big values)
  207. {
  208. bit_rate=s.bitRate();
  209. }
  210. if(codec && s.codecName()!=CodecName(codec) // compare names instead of value, to ignore audio container and just check codec, because names always return just the codec
  211. || bit_rate>0 && Abs(bit_rate-s.bitRate())>8*1000 // allow 8 kbit tolerance
  212. || start_sample>0 // if we want to skip some data
  213. || length_sample!=s.samples() // if we want to skip some data
  214. || apply_vol // if we want to change volume
  215. || hz!=s.frequency() // want to change frequency
  216. )
  217. {
  218. if(codec!=SOUND_WAV
  219. && !Equal(CodecName(codec), "opus")
  220. && !Equal(CodecName(codec), "vorbis")
  221. )codec=SOUND_SND_OPUS; // if selected codec is not supported then use a default one
  222. if(bit_rate<=0) // use default bit rate
  223. {
  224. if(Equal(CodecName(codec), "opus" ))bit_rate=long(DefaultOpusBitRate ) * s.frequency()/44100 * s.channels()/2;else
  225. if(Equal(CodecName(codec), "vorbis"))bit_rate=long(DefaultVorbisBitRate) * s.frequency()/44100 * s.channels()/2;
  226. }
  227. int bit_rate_hq=Max(long(DefaultHQBitRate) * s.frequency()/44100 * s.channels()/2, bit_rate);
  228. raw.writeMem();
  229. byte temp[65536];
  230. if(!s.pos(start_sample*s.block()))goto error;
  231. long size=length_sample*s.block();
  232. if(codec==SOUND_WAV)
  233. {
  234. SndRawEncoder encoder; if(encoder.create(raw, hz, s.channels(), length_sample))
  235. {
  236. for(; size>0; )
  237. {
  238. int r=s.set(temp, Min(size, (int)SIZE(temp))); if(r<=0)goto error;
  239. if(apply_vol && !ApplyVolume(temp, r, volume))goto error;
  240. if(!encoder.encode(temp, r))goto error; size-=r;
  241. }
  242. if(!encoder.finish())goto error; return true;
  243. }
  244. }else
  245. if(Equal(CodecName(codec), "opus"))
  246. {
  247. SndOpusEncoder encoder; if(encoder.create(raw, length_sample, hz, s.channels(), bit_rate_hq)) // prioritize first frame (to avoid pop/clicks for looped audio)
  248. {
  249. int frame_size=encoder.block()*encoder.frameSamples();
  250. long raw_start=raw.pos();
  251. bool first=true;
  252. for(; size>0; )
  253. {
  254. int r=Min(size, (int)SIZE(temp));
  255. if(first)MIN(r, frame_size); // make sure we encode only the first frame with higher bit rate
  256. r=s.set(temp, r);
  257. if(r<=0)goto error;
  258. if(apply_vol && !ApplyVolume(temp, r, volume))goto error;
  259. if(!encoder.encode(temp, r))goto error; size-=r;
  260. if(first && raw.pos()!=raw_start) // if wrote the first frame
  261. {
  262. first=false;
  263. encoder.bitRate(bit_rate); // set actual desired bit rate
  264. }
  265. }
  266. encoder.bitRate(bit_rate_hq); // prioritize last frame (to avoid pop/clicks for looped audio)
  267. if(!encoder.finish())goto error; return true;
  268. }
  269. }else
  270. if(Equal(CodecName(codec), "vorbis") && s.channels() && s.frequency())
  271. {
  272. rel_bit_rate=long(bit_rate) * 2/s.channels() * 44100/s.frequency();
  273. flt quality=VorbisBitRateToQuality(rel_bit_rate);
  274. OggVorbisEncoder encoder; if(encoder.create(raw, hz, s.channels(), quality))
  275. {
  276. for(; size>0; )
  277. {
  278. int r=s.set(temp, Min(size, (int)SIZE(temp))); if(r<=0)goto error;
  279. if(apply_vol && !ApplyVolume(temp, r, volume))goto error;
  280. if(!encoder.encode(temp, r))goto error; size-=r;
  281. }
  282. if(!encoder.finish())goto error; return true;
  283. }
  284. }
  285. }
  286. }
  287. }
  288. error:
  289. File f; if(f.readStdTry(file)){f.copy(raw.writeMem()); return true;}
  290. }break;
  291. case ELM_VIDEO:
  292. {
  293. File f; if(f.readStdTry(file)){f.copy(raw.writeMem()); return true;}
  294. }break;
  295. case ELM_FILE:
  296. {
  297. File f; if(f.readStdTry(file))f.copy(raw.writeMem());
  298. }return true; // allow replacing with empty file
  299. case ELM_CODE:
  300. {
  301. FileText f; if(f.read(file)){f.getAll(code); return true;}
  302. }break;
  303. case ELM_ANIM:
  304. {
  305. if(GetExt(file)=="anim") // EE animation
  306. {
  307. XAnimation &xanim=anims.New();
  308. if(xanim.anim.load(file)){xanim.name=GetBaseNoExt(file); return true;}
  309. }else // anim inside mesh
  310. if(EE::Import(file, null, &skel, anims, null, null, bone_names, all_nodes_as_bones)) // skeleton is needed for anims
  311. if(anims.elms()>=1) // at least 1 anim
  312. {
  313. if(anims.elms()>1)
  314. if(C TextParam *anim_name=files[0].findParam("name"))
  315. {
  316. REPA(anims)if(anims[i].name==anim_name->value) // if more than 1 then find the one with matching name
  317. {
  318. Swap(anims[0], anims[i]); // put to first place
  319. break;
  320. }
  321. anims.setNum(1); // remove all other
  322. }
  323. XAnimation &anim=anims[0];
  324. anim.anim.linear(anim.fps>=LinearAnimFpsLimit);
  325. if(files.elms())
  326. {
  327. if(anim.fps>0) // clip !! do this before looping and speed !!
  328. {
  329. C TextParam *start_frame=files[0].findParam("start_frame"),
  330. * end_frame=files[0].findParam( "end_frame");
  331. if(start_frame || end_frame) // clip animation, currently importer will already offset keyframes by 'anim.start', so if we want custom ranges, we need to revert it back
  332. anim.anim.clip(start_frame ? start_frame->asFlt()/anim.fps-anim.start : 0,
  333. end_frame ? end_frame->asFlt()/anim.fps-anim.start : anim.anim.length());
  334. }
  335. FREPA(files[0].params) // process in order
  336. {
  337. C TextParam &p=files[0].params[i]; if(p.name=="speedTime") // adjust speed for specified time range !! do this after clipping so the clip isn't affected by speed !!
  338. {
  339. Vec v=p.asVec(); flt speed=v.x, start=v.y, end=v.z;
  340. if(speed)anim.anim.scaleTime(start, end, 1/speed);
  341. }
  342. }
  343. if(C TextParam *p=files[0].findParam("loop")) // set looping !! do this after clipping so the clip isn't affected by looping !!
  344. {
  345. has_loop=true;
  346. anim.anim.loop(p->asBool());
  347. files[0].params.removeData(p); // remove this parameter because we've already applied this change to the animation, so when user modifies manually the looping, and then selectes reload, then looping won't be changed
  348. }
  349. if(C TextParam *p=files[0].findParam("speed")) // adjust speed !! do this after clipping so the clip isn't affected by speed !!
  350. if(flt speed=p->asFlt())
  351. anim.anim.length(anim.anim.length()/speed, true);
  352. }
  353. T.file=Edit::FileParams::Encode(files); // 'files' could have changed, so adjust the name so the 'elm.srcFile' is set properly
  354. anim.anim.clip(0, anim.anim.length()); // remove any keyframes outside of anim range
  355. return true;
  356. }
  357. }break;
  358. case ELM_IMAGE:
  359. {
  360. if(files.elms()==6) // special case of cube map
  361. {
  362. has_color=true ; // assume that they have color
  363. has_alpha=false; // ignore alpha for cube maps
  364. FREPA(files)
  365. {
  366. ImageEx &image=images(i); if(!ImportImage(image, files[i].name))
  367. {
  368. image.raw.pos(0); if(image.ImportTry(image.raw))
  369. {
  370. if(image.cube)image.crop(image, i*image.w()/6, 0, image.w()/6, image.h()); // crop to i-th face for cubes
  371. }
  372. }
  373. Project::TransformImage(image, files[i].params, true);
  374. }
  375. // check if any of the images were not imported
  376. REPA(images)if(!images[i].is())
  377. {
  378. Image src; raw.pos(0); if(src.ImportTry(raw)) // try extracting from existing data
  379. {
  380. bool one=(src.aspect()<Avg(1.0f, 6.0f)); // source is only 1 face, not "6 x face"
  381. REPA(images)if(!images[i].is())if(one)src.copyTry(images[i]);else src.crop(images[i], i*src.w()/6, 0, src.w()/6, src.h());
  382. }
  383. break;
  384. }
  385. raw.del();
  386. // calculate max size
  387. int size=0; REPA(images)MAX(size, Max(images[i].w(), images[i].h()));
  388. // if actually has some images
  389. if(size>0)
  390. {
  391. Image dest; if(dest.createSoftTry(size*6, size, 1, IMAGE_R8G8B8A8)) // create soft RGBA so we can use simple mem copy
  392. {
  393. // clear to zero in case some images are not found
  394. dest.clear();
  395. // insert all images into 6*2D image
  396. REPA(images)
  397. {
  398. Image &src=images[i];
  399. if(src.is() && src.copyTry(src, size, size, 1, dest.hwType(), IMAGE_SOFT, 1, FILTER_BEST, true, false, false, false, false)) // copy to the same size and hw type as dest so simple mem copy can be used
  400. if(src.lockRead())
  401. {
  402. // copy non-compressed 2D face to non-compressed 6*2D
  403. int byte_pp=ImageTI[dest.hwType()].byte_pp;
  404. REPD(y, size)
  405. {
  406. Copy(dest.data() + y*dest.pitch() + i*byte_pp*size,
  407. src .data() + y*src .pitch() , byte_pp*size);
  408. }
  409. src.unlock();
  410. }
  411. }
  412. // compress into WEBP raw data
  413. dest.ExportWEBP(raw.writeMem(), 1, 1);
  414. }
  415. }
  416. return true;
  417. }else
  418. {
  419. Str ext=GetExt(file);
  420. bool transforms=(files.elms() && files[0].params.elms()); // if want to apply any transforms
  421. if(!transforms && (ext=="jpg" || ext=="jpeg" || ext=="webp"/* || ext=="png"*/)) // images are already in accepted format (even though PNG is compressed, we can achieve much better compression with WEBP)
  422. {
  423. File f; if(!f.readStdTry(file))break; f.copy(raw.writeMem());
  424. if(ext=="jpg" || ext=="jpeg"){Image image; raw.pos(0); if(image.ImportJPG (raw)){has_color=HasColor(image); has_alpha=false ;}}else // JPG never has any alpha
  425. if(ext=="png" ){Image image; raw.pos(0); if(image.ImportPNG (raw)){has_color=HasColor(image); has_alpha=HasAlpha(image);}}else
  426. if(ext=="webp" ){Image image; raw.pos(0); if(image.ImportWEBP(raw)){has_color=HasColor(image); has_alpha=HasAlpha(image);}}
  427. return true;
  428. }else // import and export as WEBP
  429. {
  430. Image image; if(ImportImage(image, file, -1, IMAGE_SOFT, 1, true))
  431. {
  432. if(files.elms())ProjectEx::TransformImage(image, files[0].params, true);
  433. image.ExportWEBP(raw.writeMem(), 1, 1); has_color=HasColor(image); has_alpha=HasAlpha(image); return true;
  434. }
  435. }
  436. }
  437. }break;
  438. case ELM_MINI_MAP:
  439. {
  440. ImageEx image; if(ImportImage(image, file, IMAGE_R8G8B8A8, IMAGE_SOFT, 1))
  441. {
  442. Swap(images.New(), image);
  443. return true;
  444. }
  445. }break;
  446. case ELM_MTRL:
  447. {
  448. if(GetExt(file)=="mtrl") // EE mtrl
  449. {
  450. MaterialEx &m=mtrls.New(); if(m.mtrl.load(file))
  451. {
  452. m.createFrom(m.mtrl);
  453. m.process(S);
  454. return true;
  455. }
  456. }else // mtrl inside mesh
  457. if(EE::Import(file, null, null, null, SCAST(Memc<XMaterial>, mtrls), null))
  458. if(mtrls.elms()>=1) // at least 1 mtrl
  459. {
  460. if(mtrls.elms()>1)
  461. if(C TextParam *mtrl_name=files[0].findParam("name"))
  462. {
  463. REPA(mtrls)if(mtrls[i].name==mtrl_name->value) // if more than 1 then find the one with matching name
  464. {
  465. Swap(mtrls[0], mtrls[i]); // put to first place
  466. break;
  467. }
  468. mtrls.setNum(1); // remove all other
  469. }
  470. Str path=GetPath(file); FREPAO(mtrls).process(path);
  471. return true;
  472. }
  473. }break;
  474. case ELM_FONT:
  475. {
  476. return edit_font.make(font);
  477. }break;
  478. }
  479. return false;
  480. }
  481. void ImporterClass::ImportDo(Import &import, ImporterClass &importer, int thread_index)
  482. {
  483. ThreadMayUseGPUData();
  484. import.status=import.import(); // after setting this, do not operate on 'import' anymore as it will be removed
  485. ThreadFinishedUsingGPUData();
  486. }
  487. void ImporterClass::ImportElm::set(C UID &elm_id, bool remember_result) {T.elm_id=elm_id; T.remember_result=remember_result;}
  488. ImporterClass::~ImporterClass() {threads.del();}
  489. bool ImporterClass::busy()C {return threads.busy();}
  490. void ImporterClass::ImportFull(ImporterClass &ic) {Proj.drop(ic.import_files, ic.import_target); ic.import_files.clear();}
  491. void ImporterClass::ImportReplace(ImporterClass &ic) {if(ic.import_files.elms())if(Elm *elm=Proj.findElm(ic.import_target)){elm->setSrcFile(ic.import_files[0]); Proj.elmReload(elm->id); Server.setElmShort(elm->id);}}
  492. void ImporterClass::ImportAnim(ImporterClass &ic) {ic.importSpecial(ANIM );}
  493. void ImporterClass::ImportCloth(ImporterClass &ic) {ic.importSpecial(CLOTH);}
  494. void ImporterClass::ImportAdd(ImporterClass &ic) {ic.importSpecial(ADD );}
  495. void ImporterClass::create()
  496. {
  497. {
  498. Node<MenuElm> n;
  499. n.New().create("Import as child to object" , ImportFull , T).desc("This will perform standard importing, and put the element inside selected object.");
  500. n.New().create("Import as animation to object" , ImportAnim , T).desc("This will import animations only and adjust them to selected object.");
  501. n.New().create("Import as cloth/armor to object", ImportCloth , T).desc("This will import mesh as cloth/armor to the selected object,\nmaking mesh use the same skeleton as target object.");
  502. n.New().create("Import and replace object" , ImportReplace, T).desc("This will replace current object with data from the file.");
  503. n.New().create("Import and add to object" , ImportAdd , T).desc("This will import mesh and add it to current object.");
  504. Gui+=import_menu.create(n);
  505. }
  506. threads.create(true, Max(1, Cpu.threads()-1)); // leave 1 thread for the main thread
  507. }
  508. void ImporterClass::stop()
  509. {
  510. threads .cancel().wait(); // finish thread processing first before anything else
  511. imports .clear();
  512. import_queue.clear();
  513. import_files.clear();
  514. }
  515. void ImporterClass::importNew(C UID &elm_id, C UID &parent_id, C Str &file, MODE mode, ELM_TYPE type, C Str &force_name, bool remember_result)
  516. {
  517. threads.queue(imports.New().set(elm_id, parent_id, file, mode, type, force_name, remember_result), ImportDo, T);
  518. }
  519. void ImporterClass::importSpecialFile(C Str &file)
  520. {
  521. Str ext=GetExt(file);
  522. if(ExtType(ext)==EXT_MESH)importNew(import_target, import_target, file, import_mode, ELM_OBJ );else
  523. if(ext=="anim" )importNew(import_target, import_target, file, import_mode, ELM_ANIM);
  524. }
  525. void ImporterClass::importSpecialDir(C Str &path)
  526. {
  527. for(FileFind ff(path); ff(); )switch(ff.type)
  528. {
  529. case FSTD_FILE: importSpecialFile(ff.pathName()); break;
  530. case FSTD_DIR : importSpecialDir (ff.pathName()); break;
  531. }
  532. }
  533. void ImporterClass::importSpecial(MODE mode)
  534. {
  535. T.import_mode=mode;
  536. FREPA(import_files)
  537. {
  538. FileInfo fi; if(fi.getSystem(import_files[i]))switch(fi.type)
  539. {
  540. case FSTD_FILE: importSpecialFile(import_files[i]); break;
  541. case FSTD_DIR : importSpecialDir (import_files[i]); break;
  542. }
  543. }
  544. }
  545. void ImporterClass::import(Elm &target, Memc<Str> &files, C Vec2 &screen_pos)
  546. {
  547. if(files.elms())
  548. {
  549. import_files.clear(); Swap(import_files, files);
  550. import_target=target.id;
  551. import_menu.posRU(screen_pos).activate();
  552. }
  553. }
  554. bool ImporterClass::includeTex(C UID &tex_id)
  555. {
  556. if(tex_id.valid())
  557. {
  558. SyncLocker locker(lock);
  559. return texs.binaryInclude(tex_id, Compare);
  560. }
  561. return false;
  562. }
  563. void ImporterClass::excludeTex(C UID &tex_id)
  564. {
  565. if(tex_id.valid())
  566. {
  567. SyncLocker locker(lock);
  568. texs.binaryExclude(tex_id, Compare);
  569. }
  570. }
  571. void ImporterClass::clearProj()
  572. {
  573. stop();
  574. import_results.clear();
  575. {SyncLocker locker(lock); texs.clear();}
  576. }
  577. void ImporterClass::opened(Project &proj, ElmNode &node)
  578. {
  579. if(proj.needUpdate())clearProj();else
  580. {
  581. {SyncLocker locker(lock); texs=proj.texs;}
  582. investigate(node);
  583. }
  584. }
  585. ::ImporterClass::Import* ImporterClass::findImport(C UID &elm_id)
  586. {
  587. REPA(imports)
  588. {
  589. Import &import=imports[i];
  590. if(import.mode==UPDATE && import.elm_id==elm_id)return &import;
  591. }
  592. return null;
  593. }
  594. ::ImporterClass::ImportElm* ImporterClass::findImportQueue(C UID &elm_id)
  595. {
  596. FREPA(import_queue)if(import_queue[i].elm_id==elm_id)return &import_queue[i];
  597. return null;
  598. }
  599. bool ImporterClass::inQueue(C UID &elm_id)
  600. {
  601. return findImport(elm_id) || findImportQueue(elm_id);
  602. }
  603. void ImporterClass::cancelImports(C MemPtr<UID> &sorted_elm_ids)
  604. {
  605. REPA(imports) // check all 'imports' in case there are multiple imports for the same element
  606. {
  607. Import &import=imports[i]; // remember that this 'import' may be processed on secondary thread
  608. if(import.mode==UPDATE // only this mode operates on 'elm_id' while others treat it as target
  609. && sorted_elm_ids.binaryHas(import.elm_id, Compare))
  610. {
  611. if(threads.cancel(import, ImportDo, T))import.status=0; // if succesfully canceled, then mark as finished, otherwise we need to wait until it will finish on its own
  612. import.cancel=true;
  613. if(import.remember_result)*import_results(import.elm_id)=Edit::RELOAD_CANCELED; // if wanted to remember result, then set as canceled
  614. }
  615. }
  616. }
  617. void ImporterClass::cancel(C MemPtr<UID> &elm_ids)
  618. {
  619. if(elm_ids.elms())
  620. {
  621. Mems<UID> sorted; sorted=elm_ids; sorted.sort(Compare);
  622. cancelImports(sorted);
  623. REPA(import_queue)
  624. {
  625. ImportElm &import=import_queue[i];
  626. if(sorted.binaryHas(import.elm_id, Compare))
  627. {
  628. if(import.remember_result)*import_results(import.elm_id)=Edit::RELOAD_CANCELED; // if wanted to remember result, then set as canceled
  629. import_queue.remove(i, true);
  630. }
  631. }
  632. }
  633. }
  634. void ImporterClass::getResult(C MemPtr<UID> &elms, MemPtr<Edit::IDParam<Edit::RELOAD_RESULT> > results)
  635. {
  636. results.setNum(elms.elms()); // pre-alloc
  637. FREPA(results)
  638. {
  639. Edit::IDParam<Edit::RELOAD_RESULT> &result=results[i];
  640. result.id=elms[i];
  641. if(Edit::RELOAD_RESULT *r=import_results.find(result.id))result.value=*r;else
  642. {
  643. C Elm *elm=Proj.findElm(result.id);
  644. if(!elm )result.value=Edit::RELOAD_ELM_NOT_FOUND;else
  645. if( elm->importing())result.value=Edit::RELOAD_IN_PROGRESS ;else
  646. result.value=Edit::RELOAD_NOT_REQUESTED;
  647. }
  648. }
  649. }
  650. void ImporterClass::clearImportResults(C MemPtr<UID> &elm_ids)
  651. {
  652. REPA(elm_ids)import_results.removeKey(elm_ids[i]);
  653. }
  654. void ImporterClass::forgetResult(C MemPtr<UID> &elm_ids)
  655. {
  656. if(elm_ids.elms())
  657. {
  658. Mems<UID> sorted; sorted=elm_ids; sorted.sort(Compare);
  659. REPA(imports)
  660. {
  661. Import &import=imports[i]; // remember that this 'import' may be processed on secondary thread
  662. if(import.mode==UPDATE // only this mode operates on 'elm_id' while others treat it as target
  663. && import.remember_result // if wanted to remember the result
  664. && sorted.binaryHas(import.elm_id, Compare))import.remember_result=false;
  665. }
  666. REPA(import_queue)
  667. {
  668. ImportElm &import=import_queue[i];
  669. if(import.remember_result
  670. && sorted.binaryHas(import.elm_id, Compare))import.remember_result=false;
  671. }
  672. clearImportResults(sorted);
  673. }
  674. }
  675. void ImporterClass::reload(C MemPtr<UID> &elm_ids, bool remember_result)
  676. {
  677. Mems<UID> sorted; sorted=elm_ids; sorted.sort(Compare);
  678. cancelImports (sorted);
  679. if(remember_result)clearImportResults(sorted); // since we'll reload elements, we need to clear any previous results, do this after calling 'cancelImports' which may set RELOAD_CANCELED
  680. REPA(elm_ids) // add in reversed order because elements last in the queue are imported first
  681. {
  682. C UID &elm_id=elm_ids[i];
  683. if(ImportElm *elm=findImportQueue(elm_id))elm->remember_result|=remember_result;
  684. else import_queue.New().set(elm_id, remember_result);
  685. }
  686. }
  687. void ImporterClass::investigate(Elm &elm)
  688. {
  689. if(elm.importing() && !inQueue(elm.id))import_queue.New().set(elm.id);
  690. }
  691. void ImporterClass::investigate(ElmNode &node)
  692. {
  693. if(!Proj.needUpdate())
  694. {
  695. SyncLocker locker(lock);
  696. REPA(node.children) // go from back
  697. {
  698. int child_i=node.children [i];
  699. ElmNode &child =Proj.hierarchy[child_i];
  700. Elm &elm =Proj.elms [child_i];
  701. if(ImportRemovedElms || !elm.removed())
  702. {
  703. investigate(child);
  704. investigate(elm );
  705. }
  706. }
  707. }
  708. }
  709. void ImporterClass::investigate(Memc<UID> &elm_ids)
  710. {
  711. if(!Proj.needUpdate())
  712. {
  713. SyncLocker locker(lock);
  714. FREPA(elm_ids)if(Elm *elm=Proj.findElm(elm_ids[i]))if(ImportRemovedElms || elm->finalExists())
  715. {
  716. int index=Proj.elms.validIndex(elm); if(InRange(index, Proj.hierarchy))
  717. {
  718. investigate(Proj.elms [index]);
  719. investigate(Proj.hierarchy[index]);
  720. }
  721. }
  722. }
  723. }
  724. void ImporterClass::processUpdate(Import &import)
  725. {
  726. if(Elm *elm=Proj.findElm(import.elm_id))
  727. {
  728. Proj.setListCurSel();
  729. Proj.closeElm(import.elm_id);
  730. elm->importing(false);
  731. switch(elm->type)
  732. {
  733. case ELM_MTRL: if(import.mtrls.elms())
  734. {
  735. Edit::FileParams fp=import.file; fp.getParam("name").value=import.mtrls[0].name;
  736. Proj.setMtrl(*elm, import.mtrls[0], fp.encode());
  737. Server.setElmLong(elm->id);
  738. }break;
  739. case ELM_IMAGE:
  740. {
  741. elm->setSrcFile(import.file);
  742. import.raw.pos(0); Proj.imageSet(elm->id, import.raw, import.has_color, import.has_alpha);
  743. }break;
  744. case ELM_SOUND:
  745. case ELM_VIDEO:
  746. case ELM_FILE :
  747. {
  748. elm->setSrcFile(import.file);
  749. import.raw.pos(0); Proj.fileSet(elm->id, import.raw);
  750. }break;
  751. case ELM_CODE:
  752. {
  753. elm->setSrcFile(import.file);
  754. Proj.codeSet(elm->id, import.code);
  755. }break;
  756. case ELM_FONT:
  757. {
  758. Proj.elmChanging(*elm);
  759. elm->fontData()->newVer();
  760. Save(import.font, Proj.gamePath(*elm)); Proj.savedGame(*elm);
  761. Proj.elmChanged(*elm);
  762. Server.setElmLong(elm->id);
  763. }break;
  764. case ELM_ANIM:
  765. {
  766. if(import.anims.elms())
  767. if(ElmAnim *anim_data=elm->animData())
  768. {
  769. Mems<Edit::FileParams> file_params=Edit::FileParams::Decode(import.file);
  770. Animation &anim=import.anims[0].anim;
  771. anim_data->newVer();
  772. anim_data->file_time.getUTC(); // file was changed
  773. anim_data->setSrcFile(import.file);
  774. if(import.has_loop) // if import has information about looping then use it
  775. {
  776. anim_data->loop(anim.loop()); anim_data->loop_time.getUTC(); // set from imported animation
  777. }else anim.loop(anim_data->loop()); // otherwise keep the old setting
  778. anim.linear(anim_data->linear()); // keep old linear
  779. if(Elm *skel_elm=Proj.findElm(anim_data->skel_id))if(ElmSkel *skel_data=skel_elm->skelData()) // load target skeleton
  780. {
  781. anim_data->transform=skel_data->transform; // set desired transform to match target skeleton
  782. C Skeleton *skel=Skeletons(Proj.gamePath(skel_elm->id));
  783. EditSkeleton edit_skel; edit_skel.load(Proj.editPath(skel_elm->id));
  784. Matrix m=skel_data->transform();
  785. if(import.skel.is())
  786. {
  787. // transform first, in case some methods rely on correct scale
  788. import.skel.transform(m);
  789. anim.transform(m, import.skel);
  790. /* First we have to make skel/anim compatible with 'EditSkeleton', we do this by removing any extra bones from import to match 'EditSkeleton'
  791. Animation may contain extra bones not present in the Object's EditSkeleton so just remove them
  792. This is important, because if animation skeleton has "A->B->C" bones, but target has "A->C" bones, then in animations "C" bones are stored relative to "B" parent,
  793. but they need to be stored relative to "A"
  794. This is only for few special cases where Animation FBX files have more bones detected than the base Mesh+Skel FBX */
  795. Map<Str8, int> skel_to_node(CompareCI); // map that converts SkelBone name -> EditSkeleton node index
  796. bool remove=false; // if we've found any bone that isn't present in EditSkeleton and needs to be removed
  797. REPA(import.skel.bones)
  798. {
  799. int node=edit_skel.findNodeI(import.nodeName(i), import.nodeUID(i)); // use 'bone_names' if available
  800. if( node>=0)*skel_to_node(import.skel.bones[i].name)=node;else remove=true; // set only those that were found, others will be removed below
  801. }
  802. if(remove) // if we need to remove some bones
  803. {
  804. Skeleton temp=import.skel; REPA(temp.bones)if(!skel_to_node.find(temp.bones[i].name))temp.removeBone(i); // if this bone is not present in 'EditSkeleton' then remove it
  805. anim.adjustForSameTransformWithDifferentSkeleton(import.skel, temp); // this will also remove 'anim.bones' not found in skeleton
  806. Swap(temp.bones, import.skel.bones);
  807. }
  808. {
  809. // rename from skel bone names to name set from node index to match what we will set in 'temp' skeleton, for example name "leg" gets renamed to "1" if the node index==1
  810. REPA(import.skel.bones){SkelBone &bone=import.skel.bones[i]; Set(bone.name, TextInt(*skel_to_node(bone.name)));}
  811. REPA( anim.bones){AnimBone &bone= anim.bones[i]; Set(bone.name, TextInt(*skel_to_node(bone.name)));}
  812. Skeleton temp; edit_skel.set(temp); temp.transform(m);
  813. // adjust skeleton pose
  814. anim.adjustForSameSkeletonWithDifferentPose(import.skel, temp);
  815. Swap(temp.bones, import.skel.bones);
  816. }
  817. {
  818. Mems<Mems<IndexWeight> > weights; edit_skel.set(weights, import.skel, *skel, EditSkeleton::BONE_NAME_IS_NODE_INDEX);
  819. int root=edit_skel.root;
  820. if(anim_data->flag&ElmAnim::ROOT_FROM_BODY) // if want from body, then override 'root' bone index, and set custom
  821. {
  822. int bone=Max(0, skel->findBoneI(BONE_SPINE));
  823. if(InRange(bone, skel->bones))root=edit_skel.boneToNode(skel->bones[bone].name);
  824. }
  825. anim.adjustForSameTransformWithDifferentSkeleton(import.skel, *skel, root, weights, anim_data->rootFlags()|(edit_skel.rootZero() ? 0 : ROOT_BONE_POSITION|ROOT_START_IDENTITY)); // don't set ROOT_BONE_POSITION for 'rootZero' because we actually want to force zero bone position, this is needed in case original skeleton root bone pos is zero, and skeleton is transformed with custom position offset, in which case 'import.skel' root bone pos will not be zero and 'edit_skel' (in original transform) root bone pos is zero
  826. anim_data->setRoot(anim);
  827. }
  828. }else
  829. {
  830. anim.setBoneNameTypeIndexesFromSkeleton(*skel);
  831. anim.transform(m, *skel);
  832. }
  833. // optimize (after transform because scale affects position key removal, after 'adjustForSameTransformWithDifferentSkeleton' so it can operate on highest precision and to cleanup keyframes generated by it)
  834. {
  835. flt angle_eps=EPS_ANIM_ANGLE, pos_eps=EPS_ANIM_POS, scale_eps=EPS_ANIM_SCALE;
  836. if(file_params.elms())if(C TextParam *optimize=file_params[0].findParam("optimize")){flt o=optimize->asFlt(); angle_eps*=o; pos_eps*=o; scale_eps*=o;}
  837. anim.optimize(angle_eps, pos_eps, scale_eps);
  838. }
  839. // mirror
  840. {
  841. if(file_params.elms())if(C TextParam *mirror=file_params[0].findParam("mirror"))if(mirror->asBool1())anim.mirror(*skel);
  842. }
  843. }
  844. // set events, save and send
  845. Str path=Proj.gamePath(elm->id);
  846. if(!anim.events.elms()) // if new animation doesn't have events, then check old anim for them
  847. {
  848. Animation old; if(old.load(path))if(old.events.elms()) // if old had events
  849. {
  850. anim.events=old.events; // copy them
  851. if(old.length()>EPS && anim.length()>EPS)if(flt mul=anim.length()/old.length())REPAO(anim.events).time*=mul; // adjust time
  852. }
  853. }
  854. Proj.elmChanging(*elm);
  855. Save(anim, path); Proj.savedGame(*elm, path);
  856. Proj.elmChanged(*elm);
  857. Server.setElmLong(elm->id);
  858. }
  859. }break;
  860. case ELM_OBJ: if(ElmObj *obj_data=elm->objData())
  861. {
  862. obj_data->newVer();
  863. obj_data->setSrcFile(import.file);
  864. Skeleton &new_skel=import.skel;
  865. if(import.mesh.is())Proj.getObjMeshElm(elm->id, false, false); // need to have mesh element to insert mesh
  866. if( new_skel.is())Proj.getObjSkelElm(elm->id, false, false); // need to have skeleton element to insert skeleton
  867. // update
  868. if(Elm *mesh_elm=Proj.findElm(obj_data->mesh_id))
  869. if(ElmMesh *mesh_data=mesh_elm->meshData())
  870. {
  871. Edit::FileParams fp=import.file; // !! watch out because this has param 'name' changed throughout the codes !!
  872. Proj.elmChanging(* elm);
  873. if(mesh_elm)Proj.elmChanging(*mesh_elm);
  874. // update skeleton
  875. Elm *skel_elm=null;
  876. if(new_skel.is()) // only if there's a new skeleton, otherwise keep the current
  877. if(skel_elm=Proj.findElm(mesh_data->skel_id))
  878. if(ElmSkel *skel_data=skel_elm->skelData())
  879. {
  880. if(skel_elm)Proj.elmChanging(*skel_elm);
  881. skel_data->newVer();
  882. skel_data->setSrcFile(import.file);
  883. skel_data->file_time.getUTC();
  884. Matrix m=skel_data->transform();
  885. Str skel_game_path=Proj.gamePath(skel_elm->id), skel_edit_path=Proj.editPath(skel_elm->id);
  886. Skeleton *old_skel=Skeletons(skel_game_path);
  887. EditSkeleton old_edit_skel, new_edit_skel; old_edit_skel.load(skel_edit_path); new_edit_skel.create(new_skel, import.bone_names); // !! create before transform is applied !!
  888. new_skel.transform(m); // !! transform after creating 'EditSkeleton' !!
  889. // keep Skeleton params and set bone weights from new to old
  890. Mems<Mems<IndexWeight> > bone_weights; bone_weights.setNum(new_skel.bones.elms());
  891. {
  892. // slots
  893. new_skel.slots=old_skel->slots; REPA(new_skel.slots)
  894. {
  895. SkelSlot &slot=new_skel.slots[i];
  896. slot.bone=0xFF; for(int old_bone_i=slot.bone; C SkelBone *old_bone=old_skel->bones.addr(old_bone_i); old_bone_i=old_skel->boneParent(old_bone_i)) // iterate bone and its parents
  897. if(C EditSkeleton::Bone *old_edit_bone=old_edit_skel.findBone(old_bone->name))
  898. FREPA(*old_edit_bone) // iterate all Bone->Node links
  899. {
  900. int old_edit_node_i=(*old_edit_bone)[i].index;
  901. if(C EditSkeleton::Node *old_edit_node = old_edit_skel.nodes.addr(old_edit_node_i))
  902. {
  903. int new_node_bone =new_edit_skel.findNodeI(old_edit_node->name, old_edit_skel.nodeUID(old_edit_node_i)); // 'new_edit_skel' nodes have the same order/indexes as bones
  904. if( new_node_bone>=0){slot.bone=new_node_bone; goto found;}
  905. }
  906. }
  907. found:;
  908. slot.bone1=0xFF; for(int old_bone_i=slot.bone1; C SkelBone *old_bone=old_skel->bones.addr(old_bone_i); old_bone_i=old_skel->boneParent(old_bone_i)) // iterate bone and its parents
  909. if(C EditSkeleton::Bone *old_edit_bone=old_edit_skel.findBone(old_bone->name))
  910. FREPA(*old_edit_bone) // iterate all Bone->Node links
  911. {
  912. int old_edit_node_i=(*old_edit_bone)[i].index;
  913. if(C EditSkeleton::Node *old_edit_node = old_edit_skel.nodes.addr(old_edit_node_i))
  914. {
  915. int new_node_bone =new_edit_skel.findNodeI(old_edit_node->name, old_edit_skel.nodeUID(old_edit_node_i)); // 'new_edit_skel' nodes have the same order/indexes as bones
  916. if( new_node_bone>=0){slot.bone1=new_node_bone; goto found1;}
  917. }
  918. }
  919. found1:;
  920. }
  921. // bones
  922. REPA(new_skel.bones)
  923. if(InRange(i, new_edit_skel.nodes)) // 'new_edit_skel' nodes have the same order/indexes as bones
  924. {
  925. int old_node_i =old_edit_skel.findNodeI(new_edit_skel.nodes[i].name, new_edit_skel.nodeUID(i));
  926. if( old_node_i>=0)
  927. {
  928. int best_old_skel_bone_i=-1; flt weight; REPAD(b, old_edit_skel.bones)
  929. {
  930. C EditSkeleton::Bone &bone=old_edit_skel.bones[b]; REPAD(bw, bone)
  931. {
  932. C IndexWeight &iw=bone[bw]; if(iw.index==old_node_i)
  933. {
  934. int old_skel_bone_i=old_skel->findBoneI(bone.name); if(old_skel_bone_i>=0)
  935. {
  936. bone_weights[i].New().set(old_skel_bone_i, iw.weight);
  937. if(best_old_skel_bone_i<0 || iw.weight>weight){best_old_skel_bone_i=old_skel_bone_i; weight=iw.weight;}
  938. }
  939. }
  940. }
  941. }
  942. if(best_old_skel_bone_i>=0) // copy 'SkelBone' params from an old bone with highest weight
  943. {
  944. C SkelBone & src_bone=old_skel->bones[best_old_skel_bone_i];
  945. SkelBone &dest_bone=new_skel.bones[i];
  946. dest_bone.width=src_bone.width;
  947. //dest_bone.frac =src_bone.frac ;
  948. dest_bone.flag =src_bone.flag ;
  949. }
  950. }
  951. }
  952. }
  953. Proj.adjustAnimations(skel_elm->id, old_edit_skel, *old_skel, new_skel, bone_weights); // !! convert animations before saving skeleton which modifies 'old_skel' which is a pointer to cache element !!
  954. Save(new_edit_skel, skel_edit_path);
  955. Save(new_skel , skel_game_path); Proj.savedGame(*skel_elm, skel_game_path);
  956. Server.setElmFull(mesh_data->skel_id);
  957. // process animations (they will be available only during first import, and not for "reload", so we don't need to do any fancy conversions)
  958. FREPA(import.anims)
  959. {
  960. XAnimation &xanim=import.anims[i];
  961. Elm & anim=Proj.Project::newElm(xanim.name.is() ? xanim.name : "Animation", elm->id, ELM_ANIM);
  962. if(ElmAnim * anim_data=anim.animData())
  963. {
  964. fp.getParam("name").value=xanim.name;
  965. anim_data->newData();
  966. anim_data->skel_id =skel_elm ->id;
  967. anim_data->transform=skel_data->transform;
  968. anim_data->loop (xanim.anim.loop()).linear(xanim.anim.linear());
  969. anim_data->src_file =fp.encode();
  970. }
  971. xanim.anim.transform(m, new_skel);
  972. xanim.anim.optimize(); // optimize after transform
  973. Save(xanim.anim, Proj.gamePath(anim)); Proj.savedGame(anim);
  974. Server.setElmFull(anim.id);
  975. }
  976. }
  977. // setup materials before mesh
  978. Memt<UID> mtrls;
  979. FREPA(import.mtrls)
  980. {
  981. Elm *found_mtrl=null; int match=0;
  982. Import::MaterialEx &src=import.mtrls[i];
  983. fp.getParam("name").value=src.name; Str mtrl_src_file=fp.encode();
  984. REPA(Proj.elms) // find that material
  985. {
  986. Elm &mtrl=Proj.elms[i];
  987. if(ElmMaterial *mtrl_data=mtrl.mtrlData())if(mtrl.finalExists())
  988. {
  989. int m=0;
  990. if(mtrl.parent_id==elm->id) // material is inside this object
  991. {
  992. if(EqualPath(mtrl_data->src_file, mtrl_src_file))m=3;else // full path the same - perfect match
  993. if( GetBase(mtrl_data->src_file)==GetBase(mtrl_src_file))m=2; // base name the same
  994. }else
  995. {
  996. if(EqualPath(mtrl_data->src_file, mtrl_src_file))m=1; // full path the same, but not inside the object, perhaps this material is used for something else, prefer materials inside the object
  997. }
  998. if(m>match){match=m; found_mtrl=&mtrl;}
  999. }
  1000. }
  1001. if(!found_mtrl) // if haven't found then create a new one
  1002. {
  1003. found_mtrl=&Proj.newMtrl(src, elm->id, mtrl_src_file);
  1004. Server.setElmFull(found_mtrl->id);
  1005. }
  1006. mtrls.add(found_mtrl->id);
  1007. }
  1008. // update mesh
  1009. mesh_data->newVer();
  1010. mesh_data->setSrcFile(import.file);
  1011. mesh_data->file_time.getUTC();
  1012. int pmi=0; FREPD(l, import.mesh.lods()) // set materials
  1013. {
  1014. MeshLod &lod=import.mesh.lod(l); FREPA(lod)
  1015. {
  1016. if(InRange(pmi, import.part_mtrl_index))
  1017. {
  1018. int mtrl_index=import.part_mtrl_index[pmi];
  1019. if(InRange(mtrl_index, mtrls))lod.parts[i].material(Proj.gamePath(mtrls[mtrl_index]));
  1020. }
  1021. pmi++;
  1022. }
  1023. }
  1024. Str edit_path=Proj.editPath(mesh_elm->id);
  1025. Mesh old_mesh; if(Load(old_mesh, edit_path, Proj.game_path))KeepParams(old_mesh, import.mesh);
  1026. Save(import.mesh, edit_path, Proj.game_path); // save
  1027. // game mesh
  1028. Skeleton *body_skel; Proj.getMeshSkels(mesh_data, null, &body_skel);
  1029. Mesh game_mesh; EditToGameMesh(import.mesh, game_mesh, body_skel, Proj.getEnum(mesh_data->draw_group_id), &mesh_data->transform());
  1030. Save(game_mesh, Proj.gamePath(mesh_elm->id)); Proj.savedGame(*mesh_elm); // save
  1031. mesh_data->from(game_mesh);
  1032. Server.setElmFull(obj_data->mesh_id);
  1033. // notify about change
  1034. Proj.elmChanged(* elm);
  1035. if(mesh_elm)Proj.elmChanged(*mesh_elm);
  1036. if(skel_elm)Proj.elmChanged(*skel_elm);
  1037. // send to server
  1038. Server.setElmShort(elm->id);
  1039. }
  1040. }break;
  1041. case ELM_MINI_MAP:
  1042. {
  1043. if(import.images.elms()==1)
  1044. if(ElmMiniMap *mini_map_data=elm->miniMapData())
  1045. if(MiniMapVer *ver=Proj.miniMapVerGet(elm->id))
  1046. if(ver->images.elms())
  1047. if(int image_size=ver->settings.image_size)
  1048. {
  1049. Proj.elmChanging(*elm);
  1050. mini_map_data->newVer();
  1051. mini_map_data->setSrcFile(import.file);
  1052. RectI images=ver->images.last(); REPA(ver->images)images|=ver->images[i];
  1053. VecI2 images_size=image_size*(images.size()+1);
  1054. Image &all=import.images[0], single, temp;
  1055. if(all.copyTry(all, images_size.x, images_size.y, 1, IMAGE_R8G8B8A8, IMAGE_SOFT, 1)
  1056. && single.createSoftTry(image_size, image_size, 1, IMAGE_R8G8B8A8))
  1057. {
  1058. ver->changed=true;
  1059. ver->time.getUTC(); // update time of mini map
  1060. Server.setMiniMapSettings(elm->id, ver->settings, ver->time); // send updated mini map settings first, as images will be received only if their timestamp matches the settings
  1061. REPA(ver->images)
  1062. {
  1063. C VecI2 &image_pos=ver->images[i];
  1064. int ox=(image_pos.x-images.min.x )*image_size,
  1065. oy=( images.max.y-image_pos.y)*image_size;
  1066. REPD(y, single.h())
  1067. REPD(x, single.w())single.pixel(x, y, all.pixel(x+ox, y+oy));
  1068. if(single.copyTry(temp, -1, -1, -1, IMAGE_BC1, IMAGE_2D, 1))
  1069. {
  1070. temp.save(Proj.gamePath(elm->id).tailSlash(true)+image_pos);
  1071. Synchronizer.setMiniMapImage(elm->id, image_pos);
  1072. }
  1073. }
  1074. Server.setElmShort(elm->id);
  1075. Proj.elmChanged(*elm);
  1076. }
  1077. }
  1078. }break;
  1079. }
  1080. Proj.setList(); // refresh list because 'importing' affects elm color, and ELM_OBJ could have created new elements
  1081. }
  1082. }
  1083. void ImporterClass::processCloth(Import &import)
  1084. {
  1085. if(import.mesh.is())
  1086. if(Elm *obj_elm=Proj.findElm(import.elm_id, ELM_OBJ))
  1087. if(ElmObj *obj_data=obj_elm->objData())
  1088. if(Elm *body_elm=Proj.findElm(obj_data->mesh_id))
  1089. if(ElmMesh *body_mesh_data=body_elm->meshData())
  1090. {
  1091. if(Elm *parent=Proj.findElm(import.parent_id))parent->opened(true);
  1092. Elm &cloth=Proj.Project::newElm(GetBaseNoExt(import.file), import.parent_id, ELM_OBJ);
  1093. if(Elm *cloth_mesh=Proj.getObjMeshElm(cloth.id))
  1094. if(ElmMesh *mesh_data=cloth_mesh->meshData())
  1095. {
  1096. mesh_data->newData();
  1097. mesh_data->body_id = obj_data->mesh_id ;
  1098. mesh_data->transform=body_mesh_data->transform;
  1099. }
  1100. import.elm_id=cloth.id; // change id to cloth because we want to set it
  1101. processUpdate(import); // this will handle setting elm list and sending to server
  1102. }
  1103. }
  1104. void ImporterClass::processAnim(Import &import)
  1105. {
  1106. //if(import.skel.is()) don't check this because we can import also EE.Animation ('anim' file format) which doesn't contain a skeleton
  1107. if(import.anims.elms())
  1108. if( Elm *obj_elm=Proj.findElm(import.elm_id, ELM_OBJ))
  1109. if( ElmObj *obj_data=obj_elm->objData())
  1110. if( Elm *mesh_elm=Proj.findElm(obj_data->mesh_id))
  1111. if( ElmMesh *mesh_data=mesh_elm->meshData())
  1112. if( Elm *skel_elm=Proj.findElm(mesh_data->skel_id))
  1113. if( ElmSkel *skel_data=skel_elm->skelData())
  1114. if(C Skeleton *skel=Skeletons(Proj.gamePath(skel_elm->id)))
  1115. {
  1116. Edit::FileParams fp=import.file;
  1117. EditSkeleton edit_skel; edit_skel.load(Proj.editPath(skel_elm->id)); // 'edit_skel' is always in identity matrix
  1118. Matrix m=skel_data->transform();
  1119. if(import.skel.is())
  1120. {
  1121. // transform first, in case some methods rely on correct scale
  1122. import.skel.transform(m);
  1123. REPAO(import.anims).anim.transform(m, import.skel);
  1124. Map<Str8, int> skel_to_node(CompareCI); // map that converts SkelBone name -> EditSkeleton node index
  1125. bool remove=false; // if we've found any bone that isn't present in EditSkeleton and needs to be removed
  1126. REPA(import.skel.bones)
  1127. {
  1128. int node=edit_skel.findNodeI(import.nodeName(i), import.nodeUID(i)); // use 'bone_names' if available
  1129. if( node>=0)*skel_to_node(import.skel.bones[i].name)=node;else remove=true; // set only those that were found, others will be removed below
  1130. }
  1131. if(remove) // if we need to remove some bones
  1132. {
  1133. Skeleton temp=import.skel; REPA(temp.bones)if(!skel_to_node.find(temp.bones[i].name))temp.removeBone(i); // if this bone is not present in 'EditSkeleton' then remove it
  1134. REPAO(import.anims).anim.adjustForSameTransformWithDifferentSkeleton(import.skel, temp); // this will also remove 'anim.bones' not found in skeleton
  1135. Swap(temp.bones, import.skel.bones);
  1136. }
  1137. {
  1138. // rename from skel bone names to name set from node index to match what we will set in 'temp' skeleton, for example name "leg" gets renamed to "1" if the node index==1
  1139. REPA(import.skel.bones){SkelBone &bone=import.skel.bones[i]; Set(bone.name, TextInt(*skel_to_node(bone.name)));}
  1140. REPA(import.anims){Animation &anim=import.anims[i].anim; REPA( anim.bones){AnimBone &bone= anim.bones[i]; Set(bone.name, TextInt(*skel_to_node(bone.name)));}}
  1141. Skeleton temp; edit_skel.set(temp); temp.transform(m);
  1142. // adjust skeleton pose
  1143. REPAO(import.anims).anim.adjustForSameSkeletonWithDifferentPose(import.skel, temp);
  1144. Swap(temp.bones, import.skel.bones);
  1145. }
  1146. {
  1147. Mems<Mems<IndexWeight> > weights; edit_skel.set(weights, import.skel, *skel, EditSkeleton::BONE_NAME_IS_NODE_INDEX);
  1148. REPAO(import.anims).anim.adjustForSameTransformWithDifferentSkeleton(import.skel, *skel, edit_skel.root, weights, (edit_skel.rootZero() ? 0 : ROOT_BONE_POSITION|ROOT_START_IDENTITY));
  1149. }
  1150. }else
  1151. {
  1152. REPA(import.anims)
  1153. {
  1154. Animation &anim=import.anims[i].anim;
  1155. anim.setBoneNameTypeIndexesFromSkeleton(*skel);
  1156. anim.transform(m, *skel);
  1157. }
  1158. }
  1159. // optimize (after transform because scale affects position key removal, after 'adjustForSameTransformWithDifferentSkeleton' so it can operate on highest precision and to cleanup keyframes generated by it)
  1160. {
  1161. REPAO(import.anims).anim.optimize();
  1162. }
  1163. Proj.setListCurSel();
  1164. FREPA(import.anims)
  1165. {
  1166. XAnimation &xanim=import.anims[i];
  1167. // adjust name
  1168. if(!import.force_name.is() && import.anims.elms()==1 && (xanim.name=="Take 01" || xanim.name=="Take 001" || xanim.name=="Default Take" || xanim.name=="C4D Animation Take" || xanim.name=="mixamo.com" || xanim.name=="Unreal Take"))
  1169. {
  1170. import.force_name=Replace(GetBaseNoExt(import.file), '_', ' ');
  1171. REPA(import.force_name)if(import.force_name[i]=='@') // check for unity style file names (one anim per file, name "take 01" or "take 001"), file name "Forest Wolf@Bite Attack.FBX"
  1172. {
  1173. import.force_name.remove(0, i+1);
  1174. break;
  1175. }
  1176. }
  1177. // create element
  1178. Elm &anim=Proj.Project::newElm(import.force_name.is() ? import.force_name : xanim.name.is() ? xanim.name : GetBaseNoExt(Edit::FileParams(import.file).name), import.parent_id, ELM_ANIM);
  1179. if(ElmAnim *anim_data=anim.animData())
  1180. {
  1181. fp.getParam("name").value=xanim.name;
  1182. anim_data->newData();
  1183. anim_data->skel_id =skel_elm->id;
  1184. anim_data->transform=skel_data->transform;
  1185. anim_data->loop (xanim.anim.loop()).linear(xanim.anim.linear());
  1186. anim_data->src_file =fp.encode();
  1187. }
  1188. // save and send
  1189. Save(xanim.anim, Proj.gamePath(anim.id)); Proj.savedGame(anim);
  1190. Server.setElmFull(anim.id);
  1191. }
  1192. Proj.setList();
  1193. }
  1194. }
  1195. void ImporterClass::processAdd(Import &import)
  1196. {
  1197. Proj.closeElm(import.elm_id);
  1198. if(Elm *elm=Proj.findElm(import.elm_id))switch(elm->type)
  1199. {
  1200. case ELM_OBJ:
  1201. {
  1202. if(ElmObj *obj_data=elm->objData())
  1203. {
  1204. Proj.setListCurSel();
  1205. obj_data->newVer();
  1206. obj_data->setSrcFile(Edit::FileParams::Merge(obj_data->src_file, import.file)); // add to file list
  1207. if(import.mesh.is())Proj.getObjMeshElm(elm->id, false, false); // need to have mesh element to insert mesh
  1208. if(import.skel.is())Proj.getObjSkelElm(elm->id, false, false); // need to have skeleton element to insert skeleton
  1209. // update
  1210. if(Elm *mesh_elm=Proj.findElm(obj_data->mesh_id))
  1211. if(ElmMesh *mesh_data=mesh_elm->meshData())
  1212. {
  1213. // update skeleton
  1214. Elm *skel_elm=null; // currently this is not done
  1215. Proj.elmChanging(* elm);
  1216. if(mesh_elm)Proj.elmChanging(*mesh_elm);
  1217. if(skel_elm)Proj.elmChanging(*skel_elm);
  1218. // first setup materials
  1219. Edit::FileParams fp=import.file;
  1220. Memt<UID> mtrls;
  1221. FREPA(import.mtrls)
  1222. {
  1223. Elm *found_mtrl=null; int match=0;
  1224. Import::MaterialEx &src=import.mtrls[i];
  1225. fp.getParam("name").value=src.name; Str mtrl_src_file=fp.encode();
  1226. REPA(Proj.elms) // find that material
  1227. {
  1228. Elm &mtrl=Proj.elms[i];
  1229. if(ElmMaterial *mtrl_data=mtrl.mtrlData())if(mtrl.finalExists())
  1230. {
  1231. int m=0;
  1232. if(mtrl.parent_id==elm->id) // material is inside this object
  1233. {
  1234. if(EqualPath(mtrl_data->src_file, mtrl_src_file))m=3;else // full path the same - perfect match
  1235. if( GetBase(mtrl_data->src_file)==GetBase(mtrl_src_file))m=2; // base name the same
  1236. }else
  1237. {
  1238. if(EqualPath(mtrl_data->src_file, mtrl_src_file))m=1; // full path the same, but not inside the object, perhaps this material is used for something else, prefer materials inside the object
  1239. }
  1240. if(m>match){match=m; found_mtrl=&mtrl;}
  1241. }
  1242. }
  1243. if(!found_mtrl) // if haven't found then create a new one
  1244. {
  1245. found_mtrl=&Proj.newMtrl(src, elm->id, mtrl_src_file);
  1246. Server.setElmFull(found_mtrl->id);
  1247. }
  1248. mtrls.add(found_mtrl->id);
  1249. }
  1250. // update mesh
  1251. mesh_data->newVer();
  1252. mesh_data->setSrcFile(Edit::FileParams::Merge(mesh_data->src_file, import.file)); // add to file list
  1253. mesh_data->file_time.getUTC();
  1254. int pmi=0; FREPD(l, import.mesh.lods()) // set materials
  1255. {
  1256. MeshLod &lod=import.mesh.lod(l); FREPA(lod)
  1257. {
  1258. if(InRange(pmi, import.part_mtrl_index))
  1259. {
  1260. int mtrl_index=import.part_mtrl_index[pmi];
  1261. if(InRange(mtrl_index, mtrls))lod.parts[i].material(Proj.gamePath(mtrls[mtrl_index]));
  1262. }
  1263. pmi++;
  1264. }
  1265. }
  1266. Str edit_path=Proj.editPath(mesh_elm->id);
  1267. Mesh old_mesh; if(Load(old_mesh, edit_path, Proj.game_path))
  1268. {
  1269. old_mesh.add(import.mesh);
  1270. Swap(old_mesh, import.mesh);
  1271. }
  1272. Save(import.mesh, edit_path, Proj.game_path); // save
  1273. // game mesh
  1274. Skeleton *body_skel; Proj.getMeshSkels(mesh_data, null, &body_skel);
  1275. Mesh game_mesh; EditToGameMesh(import.mesh, game_mesh, body_skel, Proj.getEnum(mesh_data->draw_group_id), &mesh_data->transform());
  1276. Save(game_mesh, Proj.gamePath(mesh_elm->id)); Proj.savedGame(*mesh_elm); // save
  1277. mesh_data->from(game_mesh);
  1278. // notify about change
  1279. Proj.elmChanged(* elm);
  1280. if(mesh_elm)Proj.elmChanged(*mesh_elm);
  1281. if(skel_elm)Proj.elmChanged(*skel_elm);
  1282. // send to server
  1283. Server.setElmFull(obj_data->mesh_id); Server.setElmFull(mesh_data->skel_id); Server.setElmShort(elm->id);
  1284. }
  1285. Proj.setList(); // refresh because object may have added new elements
  1286. }
  1287. }break;
  1288. }
  1289. }
  1290. void ImporterClass::update()
  1291. {
  1292. // process imported
  1293. REPA(imports)
  1294. {
  1295. Import &import=imports[i]; if(import.status>=0) // finished importing
  1296. {
  1297. if(!import.cancel)
  1298. {
  1299. if(import.remember_result)*import_results(import.elm_id)=(import.status ? Edit::RELOAD_SUCCESS : Edit::RELOAD_FAILED);
  1300. if(import.status)switch(import.mode) // ok
  1301. {
  1302. case UPDATE: processUpdate(import); break;
  1303. case ANIM : processAnim (import); break;
  1304. case CLOTH : processCloth (import); break;
  1305. case ADD : processAdd (import); break;
  1306. }
  1307. }
  1308. imports.removeValid(i);
  1309. }
  1310. }
  1311. // process queue
  1312. for(; import_queue.elms() && imports.elms()<Max(threads.threads()*2, 15); ) // queue 15 elements per frame, so import threads can process before waiting for this code to execute on the next frame
  1313. {
  1314. C ImportElm &import=import_queue.last();
  1315. if(Elm *elm=Proj.findElm(import.elm_id))if(ImportRemovedElms || elm->finalExists())
  1316. importNew(elm->id, elm->parent_id, elm->srcFile(), UPDATE, elm->type, S, import.remember_result);
  1317. import_queue.removeLast();
  1318. }
  1319. }
  1320. ImporterClass::ImporterClass() : import_results(Compare), import_target(UIDZero), import_mode(UPDATE) {}
  1321. ImporterClass::Import::Import() : status(-1), has_loop(false), cancel(false), remember_result(false), has_color(true), has_alpha(true), ignore_anims(false), type(ELM_NONE), mode(UPDATE), elm_id(UIDZero), parent_id(UIDZero) {}
  1322. ImporterClass::Import::MaterialEx::MaterialEx() : base_0_id(UIDZero), base_1_id(UIDZero), detail_id(UIDZero), macro_id(UIDZero), reflection_id(UIDZero), light_id(UIDZero) {}
  1323. ImporterClass::Import::ImageEx::ImageEx() : cube(false) {}
  1324. /******************************************************************************/