Merge Similar Materials.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. /******************************************************************************/
  2. #include "stdafx.h"
  3. /******************************************************************************/
  4. MergeSimilarMaterials MSM;
  5. Memc<IDReplace> ReplaceIDs;
  6. State IDReplaceState(UpdateIDReplace, DrawIDReplace, InitIDReplace, ShutIDReplace);
  7. C IDReplace* ReplaceID(C UID &src, Memc<IDReplace> &replace) {return src.valid() ? replace.binaryFind(src, IDReplace::Compare) : null;}
  8. /******************************************************************************/
  9. bool ThreadIDReplace(Thread &thread)
  10. {
  11. ThreadMayUseGPUData();
  12. FREPA(Proj.elms)
  13. {
  14. if(thread.wantStop())return false;
  15. Elm &elm=Proj.elms[i]; if(elm.data)
  16. {
  17. if( ReplaceID(elm. id))elm.setRemoved(true ); // if this element is replaced with another one, then remove it
  18. if(C IDReplace *parent=ReplaceID(elm.parent_id))elm.setParent (parent->to); // if parent of this element is being removed, then assign this element to the replacement
  19. FREPA(ReplaceIDs)if(elm.data->mayContain(ReplaceIDs[i].from))
  20. {
  21. bool changed=false;
  22. switch(elm.type)
  23. {
  24. case ELM_MESH:
  25. {
  26. Mesh mesh; if(Load(mesh, Proj.editPath(elm), Proj.game_path))
  27. {
  28. REPD (l, mesh.lods( ))
  29. REPAD(p, mesh.lod (l))
  30. {
  31. MeshPart &part=mesh.lod(l).parts[p];
  32. bool changed_multi_mtrl=false;
  33. MaterialPtr mtrls[4]; REPA(mtrls)
  34. {
  35. MaterialPtr &mtrl=mtrls[i];
  36. mtrl=part.multiMaterial(i);
  37. if(C IDReplace *id=ReplaceID(mtrl.id())){changed_multi_mtrl=true; mtrl=Proj.gamePath(id->to);}
  38. }
  39. if(changed_multi_mtrl)
  40. {
  41. changed=true;
  42. part.multiMaterial(mtrls[0], mtrls[1], mtrls[2], mtrls[3]);
  43. }
  44. REP(part.variations())if(i)
  45. if(C IDReplace *id=ReplaceID(part.variation(i).id())){changed=true; part.variation(i, Proj.gamePath(id->to));}
  46. }
  47. if(changed)
  48. {
  49. ElmMesh *mesh_data=elm.meshData();
  50. mesh_data->newVer();
  51. mesh_data->fromMtrl(mesh);
  52. mesh_data->file_time.getUTC();
  53. Skeleton *body_skel; Proj.getMeshSkels(mesh_data, null, &body_skel);
  54. Mesh game; EditToGameMesh(mesh, game, body_skel, Proj.getEnum(mesh_data->draw_group_id), &mesh_data->transform());
  55. Save(mesh, Proj.editPath(elm), Proj.game_path);
  56. Save(game, Proj.gamePath(elm)); Proj.savedGame(elm);
  57. }
  58. }
  59. }break;
  60. case ELM_OBJ:
  61. case ELM_OBJ_CLASS:
  62. {
  63. EditObject obj; if(obj.load(Proj.editPath(elm)))
  64. {
  65. REPA(obj)
  66. {
  67. EditParam &param=obj[i];
  68. if(ParamTypeID(param.type))REP(param.IDs())if(ReplaceID(param.asID(i))) // if any ID needs to be replaced
  69. {
  70. Memt<UID> ids;
  71. FREP(param.IDs()) // list in original order
  72. {
  73. UID id=param.asID(i); if(C IDReplace *replace=ReplaceID(id))id=replace->to;
  74. ids.add(id);
  75. }
  76. changed=true;
  77. param.setAsIDArray(ids);
  78. break;
  79. }
  80. }
  81. if(changed)
  82. {
  83. elm.data->newVer();
  84. Save(obj, Proj.editPath(elm));
  85. Proj.makeGameVer(elm);
  86. }
  87. }
  88. }break;
  89. case ELM_WORLD:
  90. {
  91. // TODO: replace terrain materials (watch out for heightmap material palette having both replacement and original)
  92. // TODO: replace world object params
  93. }break;
  94. }
  95. if(changed)Proj.elmChanged(elm); // this may cause conflicts with 'Gui.update' (if needed then move to 'ShutIDReplace')
  96. break;
  97. }
  98. }
  99. UpdateProgress.set(i, Proj.elms.elms());
  100. }
  101. return false;
  102. }
  103. bool InitIDReplace()
  104. {
  105. SetKbExclusive();
  106. Proj.pause();
  107. UpdateProgress.create(Rect_C(0, -0.05f, 1, 0.045f));
  108. UpdateThread .create(ThreadIDReplace);
  109. return true;
  110. }
  111. void ShutIDReplace()
  112. {
  113. UpdateThread .del();
  114. UpdateProgress.del();
  115. Proj.refresh().resume();
  116. WindowSetNormal();
  117. WindowFlash();
  118. }
  119. /******************************************************************************/
  120. bool UpdateIDReplace()
  121. {
  122. if(Kb.bp(KB_ESC)){SetProjectState(); Gui.msgBox(S, "Merging breaked on user request");}
  123. if(!UpdateThread.active())SetProjectState();
  124. WindowSetProgress(UpdateProgress());
  125. Time.wait(1000/30);
  126. //Gui.update(); this may cause conflicts with 'Proj.elmChanged'
  127. Server.update(null, true);
  128. if(Ms.bp(3))WindowToggle();
  129. return true;
  130. }
  131. /******************************************************************************/
  132. void DrawIDReplace()
  133. {
  134. D.clear(BackgroundColor());
  135. D.text(0, 0.05f, "Merging Elements");
  136. GuiPC gpc;
  137. gpc.visible=gpc.enabled=true;
  138. gpc.client_rect=gpc.clip.set(-D.w(), -D.h(), D.w(), D.h());
  139. gpc.offset.zero();
  140. UpdateProgress.draw(gpc);
  141. D.clip();
  142. }
  143. /******************************************************************************/
  144. /******************************************************************************/
  145. void IDReplace::set(C UID &from, C UID &to) {T.from=from; T.to=to;}
  146. int IDReplace::Compare(C IDReplace &a, C IDReplace &b) {return ::Compare(a.from, b.from);}
  147. int IDReplace::Compare(C IDReplace &a, C UID &b) {return ::Compare(a.from, b );}
  148. bool MergeSimilarMaterials::Mtrl::similar(C Mtrl &m)C
  149. {
  150. if(MSM. name && name!=m. name)return false;
  151. if(MSM. color_name && color_name!=m. color_name)return false;
  152. if(MSM. color_tex && base_0_tex!=m. base_0_tex)return false;
  153. if(MSM. normal_tex && base_1_tex!=m. base_1_tex)return false;
  154. if(MSM. detail_tex && detail_tex!=m. detail_tex)return false;
  155. if(MSM. macro_tex && macro_tex!=m. macro_tex)return false;
  156. if(MSM. reflect_tex && reflect_tex!=m.reflect_tex)return false;
  157. if(MSM. light_tex && light_tex!=m. light_tex)return false;
  158. if(MSM. cull && cull!=m. cull)return false;
  159. if(MSM. tech && tech!=m. tech)return false;
  160. if(MSM. color_value_on() && Abs(col -m.col ).max()>MSM. color_value)return false;
  161. if(MSM. bump_value_on() && Abs(bump -m.bump ) >MSM. bump_value)return false;
  162. if(MSM. spec_value_on() && Abs(spec -m.spec ) >MSM. spec_value)return false;
  163. if(MSM. glow_value_on() && Abs(glow -m.glow ) >MSM. glow_value)return false;
  164. if(MSM.reflect_value_on() && Abs(reflect -m.reflect ) >MSM.reflect_value)return false;
  165. if(MSM. uv_scale_on() && Abs(uv_scale-m.uv_scale) >MSM. uv_scale)return false;
  166. return true;
  167. }
  168. ::MergeSimilarMaterials::Mtrl& MergeSimilarMaterials::Mtrl::set(C UID &elm_id, C EditMaterial &m)
  169. {
  170. if(Elm *elm=Proj.findElm(elm_id))name=elm->name;
  171. T.elm_id=elm_id;
  172. color_name=GetBase(Edit::FileParams(m.color_map).name);
  173. base_0_tex=m.base_0_tex;
  174. base_1_tex=m.base_1_tex;
  175. detail_tex=m.detail_tex;
  176. macro_tex=m.macro_tex;
  177. reflect_tex=m.reflection_tex;
  178. light_tex=m.light_tex;
  179. cull=m.cull;
  180. tech=m.tech;
  181. col=m.color;
  182. bump=m.bump;
  183. spec=m.specular;
  184. glow=m.glow;
  185. reflect=m.reflection;
  186. uv_scale=m.tex_scale;
  187. return T;
  188. }
  189. int MergeSimilarMaterials::CompareMtrl(C Mtrl &a, C Mtrl &b) {return ComparePathNumber(Proj.elmFullName(b.elm_id), Proj.elmFullName(a.elm_id));}
  190. void MergeSimilarMaterials::Detect(MergeSimilarMaterials &msm)
  191. {
  192. MtrlEdit.flush(); // flush Material Editor changes first
  193. Memc<Mtrl> mtrls;
  194. Memc<Memc<Mtrl> > similar;
  195. // first load all existing materials
  196. FREPA(Proj.elms)
  197. {
  198. Elm &elm=Proj.elms[i]; if(elm.type==ELM_MTRL && elm.finalExists())
  199. {
  200. EditMaterial edit_mtrl;
  201. if(edit_mtrl.load(Proj.editPath(elm)))
  202. if(!msm.color_is || edit_mtrl.base_0_tex.valid())
  203. mtrls.New().set(elm.id, edit_mtrl);
  204. }
  205. }
  206. mtrls.sort(CompareMtrl);
  207. // detect similar ones
  208. for(; mtrls.elms(); )
  209. {
  210. Mtrl &mtrl=mtrls.last();
  211. // first check in already merged groups
  212. REPA(similar)
  213. {
  214. Memc<Mtrl> &group=similar[i];
  215. if(mtrl.similar(group[0])) // compare with the first one in the group only
  216. {
  217. Swap(group.New(), mtrl);
  218. mtrls.removeLast();
  219. goto merged;
  220. }
  221. }
  222. // now check all single materials
  223. REPD(i, mtrls.elms()-1)
  224. {
  225. Mtrl &test=mtrls[i];
  226. if(mtrl.similar(test)) // 2 materials are similar
  227. {
  228. Memc<Mtrl> &group=similar.New(); // create a new group
  229. Swap(group.New(), mtrl);
  230. Swap(group.New(), test);
  231. mtrls.removeLast( ); // first remove the one with higher index
  232. mtrls.remove (i, true); // now remove the one with lower index
  233. goto merged;
  234. }
  235. }
  236. mtrls.removeLast(); // no similar were detected so just remove it
  237. merged:;
  238. }
  239. // setup data
  240. MSM.replace.clear();
  241. MSM.data .clear();
  242. FREPA(similar)
  243. {
  244. C Memc<Mtrl> &src=similar[i];
  245. for(int i=1; i<src.elms(); i++)MSM.replace.New().set(src[i].elm_id, src[0].elm_id);
  246. if(MSM.data.elms())MSM.data.New(); FREPA(src)MSM.data.New().id=src[i].elm_id;
  247. }
  248. MSM.replace.sort(IDReplace::Compare);
  249. MSM.list.setData(MSM.data);
  250. }
  251. void MergeSimilarMaterials::display(C MemPtr<UID> &elm_ids)
  252. {
  253. Memc<UID> mtrl_ids; REPA(elm_ids)if(Elm *mtrl=Proj.findElm(elm_ids[i], ELM_MTRL))mtrl_ids.add(mtrl->id);
  254. if(mtrl_ids.elms()>1)
  255. {
  256. replace .clear();
  257. data .clear();
  258. mtrl_ids.sort(CompareProjPath);
  259. FREPA(mtrl_ids)
  260. {
  261. if(i)replace.New().set(mtrl_ids[i], mtrl_ids[0]);
  262. data.New().id=mtrl_ids[i];
  263. }
  264. replace.sort(IDReplace::Compare);
  265. list.setData(data);
  266. activate();
  267. }
  268. }
  269. void MergeSimilarMaterials::drag(Memc<UID> &elms, GuiObj *focus_obj, C Vec2 &screen_pos)
  270. {
  271. if(contains(focus_obj))
  272. {
  273. display(elms);
  274. elms.clear();
  275. }
  276. }
  277. void MergeSimilarMaterials::Merge(MergeSimilarMaterials &msm)
  278. {
  279. if(msm.replace.elms())
  280. {
  281. ReplaceIDs=msm.replace;
  282. msm.clearProj();
  283. IDReplaceState.set(StateFadeTime);
  284. }
  285. }
  286. Str MergeSimilarMaterials::Data::AsText(C Data &data) {return Proj.elmFullName(data.id);}
  287. void MergeSimilarMaterials::clearProj()
  288. {
  289. replace.clear();
  290. data .clear();
  291. list .clear();
  292. }
  293. void MergeSimilarMaterials::create()
  294. {
  295. add("Require Same:");
  296. add("Material Name" , MEMBER(MergeSimilarMaterials, name)).desc("Materials will be tested against their name in the project");
  297. add("Color Source Name", MEMBER(MergeSimilarMaterials, color_name)).desc("Materials will be tested against the source file name of the color texture.\nOnly the base part of the file name (without the path) is checked.");
  298. add("Color Texture" , MEMBER(MergeSimilarMaterials, color_tex )).desc("Materials will be tested against the actual color texture image");
  299. add("Normal Texture" , MEMBER(MergeSimilarMaterials, normal_tex));
  300. add("Detail Texture" , MEMBER(MergeSimilarMaterials, detail_tex));
  301. add("Macro Texture" , MEMBER(MergeSimilarMaterials, macro_tex));
  302. add("Reflect Texture" , MEMBER(MergeSimilarMaterials, reflect_tex));
  303. add("Light Texture" , MEMBER(MergeSimilarMaterials, light_tex));
  304. add("Technique" , MEMBER(MergeSimilarMaterials, tech));
  305. add("Cull" , MEMBER(MergeSimilarMaterials, cull));
  306. add("Color Value" , MEMBER(MergeSimilarMaterials, color_value )).desc("Tolerance").range(0, 2);
  307. add("Bump Value" , MEMBER(MergeSimilarMaterials, bump_value )).desc("Tolerance").range(0, 1);
  308. add("Specular Value" , MEMBER(MergeSimilarMaterials, spec_value )).desc("Tolerance").range(0, 1);
  309. add("Glow Value" , MEMBER(MergeSimilarMaterials, glow_value )).desc("Tolerance").range(0, 1);
  310. add("Reflection Value" , MEMBER(MergeSimilarMaterials, reflect_value)).desc("Tolerance").range(0, 1);
  311. add("UV Scale" , MEMBER(MergeSimilarMaterials, uv_scale )).desc("Tolerance").min (0);
  312. add();
  313. add("Merge Only If:");
  314. add("Color Texture Exists", MEMBER(MergeSimilarMaterials, color_is)).desc("When this option is selected, then Materials will not be merged if they don't have a color texture image set");
  315. flt h=0.043f;
  316. ::PropWin::create("Merge Similar Materials", Vec2(0.02f, -0.02f), 0.036f, h, 0.15f); button[2].func(HideProjAct, SCAST(GuiObj, T)).show();
  317. int c=0;
  318. FREPA(props)if(props[i].textline.is())
  319. {
  320. CheckBox *check=null;
  321. switch(c++)
  322. {
  323. case 0: check=& color_value_on; break;
  324. case 1: check=& bump_value_on; break;
  325. case 2: check=& spec_value_on; break;
  326. case 3: check=& glow_value_on; break;
  327. case 4: check=&reflect_value_on; break;
  328. case 5: check=& uv_scale_on; break;
  329. }
  330. if(check)T+=check->create(Rect_LU(props[i].textline.pos(), props[i].textline.size().y), true);
  331. props[i].textline.move(Vec2(h, 0));
  332. props[i].button .move(Vec2(h, 0));
  333. }
  334. autoData(this);
  335. Vec2 params=rect().size(); params.x+=h; Vec2 size=params; size.x+=1.3f; size.y+=0.6f;
  336. rect(Rect_C(0, size));
  337. T+=detected.create(Vec2(Avg(params.x, size.x)-0.05f, -0.04f), "Detected:", &ts);
  338. T+=detect.create(Rect_D(size.x*1/3, -clientHeight()+0.04f, 0.3f, 0.06f), "Detect").func(Detect, T).desc("Detect similar materials");
  339. T+= merge.create(Rect_D(size.x*2/3, -clientHeight()+0.04f, 0.3f, 0.06f), "Merge" ).func(Merge , T).desc("Merge detected materials");
  340. T+=region.create(Rect(params.x, detect.rect().max.y+0.04f, size.x-0.02f, detected.pos().y-0.03f));
  341. ListColumn lc[]=
  342. {
  343. ListColumn(Data::AsText, LCW_DATA, "File"),
  344. };
  345. region+=list.create(lc, Elms(lc), true).elmHeight(0.038f).textSize(0, 1); FlagDisable(list.flag, LIST_SORTABLE); list.cur_mode=LCM_MOUSE;
  346. }
  347. void MergeSimilarMaterials::update(C GuiPC &gpc)
  348. {
  349. ::EE::ClosableWindow::update(gpc);
  350. if(visible() && gpc.visible)
  351. {
  352. if(Ms.tappedFirst(0) && Gui.ms()==&list)if(Data *data=list())if(Elm *elm=Proj.findElm(data->id))MtrlEdit.toggle(elm);
  353. }
  354. }
  355. MergeSimilarMaterials::MergeSimilarMaterials() : name(false), color_name(false), color_is(false), color_tex(true), normal_tex(true), detail_tex(true), macro_tex(true), reflect_tex(true), light_tex(true), tech(true), cull(true), color_value(0.1f), bump_value(0.1f), spec_value(0.1f), glow_value(0.1f), reflect_value(0.1f), uv_scale(0.1f) {}
  356. MergeSimilarMaterials::Data::Data() : id(UIDZero) {}
  357. /******************************************************************************/