Convert To Atlas.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. /******************************************************************************/
  2. #include "stdafx.h"
  3. /******************************************************************************/
  4. ConvertToAtlasClass ConvertToAtlas;
  5. /******************************************************************************/
  6. /******************************************************************************/
  7. cchar8 *ConvertToAtlasClass::mode_t[]=
  8. {
  9. "Create as new objects (use for testing)",
  10. "Replace existing objects (and keep Original Elements)",
  11. "Replace existing objects (and Disable Publishing of Original Elements)",
  12. "Replace existing objects (and Remove Original Elements)",
  13. };
  14. const flt ConvertToAtlasClass::h=0.05f;
  15. /******************************************************************************/
  16. void ConvertToAtlasClass::Mtrl::setScaleText(C VecI2 &size) {t_scaled_size.set(TexSize(size));}
  17. void ConvertToAtlasClass::Mtrl::setScale( ) {scaled_size=Round(original_size*scale*ConvertToAtlas.scale); setScaleText(scaled_size);}
  18. void ConvertToAtlasClass::Mtrl::setScale(flt scale ) {T.scale=scale; setScale();}
  19. void ConvertToAtlasClass::Mtrl::posY(flt y )
  20. {
  21. del .pos(Vec2(del .pos().x, y));
  22. name.pos(Vec2(name.pos().x, y));
  23. y-=del.rect().h()/2;
  24. prop .pos(Vec2(prop.name .pos().x, y));
  25. t_original_size.pos(Vec2(t_original_size.pos().x, y));
  26. t_scaled_size .pos(Vec2(t_scaled_size .pos().x, y));
  27. }
  28. void ConvertToAtlasClass::Mtrl::pack(MtrlImages &atlas)
  29. {
  30. ThreadMayUseGPUData();
  31. edit.load(Proj.editPath(id));
  32. VecI2 size=packed_rect.size(); if(rotated)size.swap();
  33. MtrlImages src; src.fromMaterial(edit, Proj, false, size, true);
  34. Color normal(128, 128, 255);
  35. REPD(y, size.y)
  36. REPD(x, size.x)
  37. {
  38. int dest_x=x, dest_y=y; if(rotated)Swap(dest_x, dest_y);
  39. dest_x+=packed_rect.min.x;
  40. dest_y+=packed_rect.min.y;
  41. flt glow; if(src.glow .is()){Color c=src.glow.color(x, y); glow=c.lum()*c.a/255.0f;}else glow=255;
  42. Color n ; if(src.normal.is())
  43. {
  44. n=src.normal.color(x, y);
  45. if(src.flip_normal_y)n.g=255-n.g;
  46. if(rotated)Swap(n.r, n.g); // in rotated mode, normal XY channels need to be swapped
  47. if(!Equal(edit.rough, 1))
  48. {
  49. n.r=Mid(Round((n.r-128)*edit.rough+128), 0, 255);
  50. n.g=Mid(Round((n.g-128)*edit.rough+128), 0, 255);
  51. }
  52. }else n=normal;
  53. Color c; if(src.color.is())
  54. {
  55. Vec4 cf=src.color.colorF(x, y);
  56. cf.xyz*=edit.color.xyz;
  57. c=cf;
  58. }else c=WHITE;
  59. atlas.color .color(dest_x, dest_y, c);
  60. atlas.alpha .pixB (dest_x, dest_y)= (src.alpha .is() ? Mid(Round(src.alpha .pixelF(x, y)*255 ), 0, 255) : 255);
  61. atlas.bump .pixB (dest_x, dest_y)= (src.bump .is() ? Mid(Round(src.bump .pixelF(x, y)*255 ), 0, 255) : 255);
  62. atlas.specular.pixB (dest_x, dest_y)=Mid(Round((src.specular.is() ? src.specular.color (x, y).lum() : 255)*edit.specular), 0, 255); // bake
  63. atlas.glow .pixB (dest_x, dest_y)=Mid(Round( glow *edit.glow ), 0, 255); // bake
  64. atlas.normal .color(dest_x, dest_y, n);
  65. }
  66. tex=src.color.is()*BT_COLOR | src.alpha.is()*BT_ALPHA | src.bump.is()*BT_BUMP | src.normal.is()*BT_NORMAL | src.specular.is()*BT_SPECULAR | src.glow.is()*BT_GLOW;
  67. AtomicOr(atlas.tex, tex);
  68. }
  69. void ConvertToAtlasClass::Preview::draw(C GuiPC &gpc)
  70. {
  71. if(visible() && gpc.visible)
  72. {
  73. D.clip(gpc.clip);
  74. Rect r=rect()+gpc.offset;
  75. if(ConvertToAtlas.tex_size.all())
  76. {
  77. r=Fit(flt(ConvertToAtlas.tex_size.x)/ConvertToAtlas.tex_size.y, r);
  78. ALPHA_MODE alpha=D.alpha(ALPHA_NONE);
  79. REPA(ConvertToAtlas.mtrls)
  80. {
  81. C Mtrl &mtrl=ConvertToAtlas.mtrls[i];
  82. Rect mr =mtrl.packed_rect; mr/=Vec2(ConvertToAtlas.tex_size);
  83. mr.setY(1-mr.max.y, 1-mr.min.y); // inverse Y because textures coordinates increasing down, and gui drawing increasing up
  84. mr.min=r.lerp(mr.min);
  85. mr.max=r.lerp(mr.max);
  86. if(Image *image=mtrl.base_0())
  87. {
  88. if(mtrl.rotated)image->drawVertical(mr);
  89. else image->draw (mr);
  90. }
  91. mr.draw(RED, false);
  92. }
  93. D.alpha(alpha);
  94. }else
  95. if(ConvertToAtlas.mtrls.elms())
  96. {
  97. D.text(r, "Textures too big\nDecrease scale");
  98. }
  99. r.draw(Gui.borderColor(), false);
  100. }
  101. }
  102. Str ConvertToAtlasClass::Warning::MeshName(C Error &error) {return Proj.elmFullName(error.mesh_id);}
  103. Str ConvertToAtlasClass::Warning::MtrlName(C Error &error) {return Proj.elmFullName(error.mtrl_id);}
  104. void ConvertToAtlasClass::Warning::Proceed(ptr) {ConvertToAtlas.convertPerform();}
  105. void ConvertToAtlasClass::Warning::create()
  106. {
  107. Gui+=::EE::Window::create(Rect_C(0, 0, 1.7f, 1), "Convert To Atlas").hide(); button[2].show();
  108. T+=text.create(Vec2(clientWidth()/2, -0.06f), "Warning: Following meshes use wrapped texture coordinates.\nConverting to Atlas may introduce some artifacts.");
  109. T+=proceed.create(Rect_D(clientWidth()/2, -clientHeight()+0.03f, 0.34f, 0.055f), "Proceed Anyway").func(Proceed);
  110. T+=region.create(Rect(0, proceed.rect().max.y, clientWidth(), text.rect().min.y-0.04f).extend(-0.02f, -0.03f));
  111. ListColumn lc[]=
  112. {
  113. ListColumn(MeshName, LCW_DATA, "Mesh"),
  114. ListColumn(MtrlName, LCW_DATA, "Material"),
  115. ListColumn(MEMBER(Error, error), 0.18f, "UV Error"),
  116. };
  117. region+=list.create(lc, Elms(lc));
  118. }
  119. void ConvertToAtlasClass::Warning::display()
  120. {
  121. list.setData(ConvertToAtlas.errors);
  122. activate();
  123. }
  124. Str ConvertToAtlasClass::TexSize(C VecI2 &size) {return S+size.x+'x'+size.y;}
  125. void ConvertToAtlasClass::Scale(Mtrl &mtrl, C Str &text) {mtrl.setScale(TextFlt(text)); ConvertToAtlas.refresh();}
  126. void ConvertToAtlasClass::Del(Mtrl &mtrl) {ConvertToAtlas.mtrls.removeData(&mtrl, true); ConvertToAtlas.refresh();}
  127. void ConvertToAtlasClass::Refresh(C Property &prop) {ConvertToAtlas.refresh();}
  128. void ConvertToAtlasClass::ChangedScale(C Property &prop) {REPAO(ConvertToAtlas.mtrls).setScale(); ConvertToAtlas.refresh();}
  129. int ConvertToAtlasClass::CompareData(C Mtrl &a, C Mtrl &b) {return ComparePathNumber(a.name(), b.name());}
  130. void ConvertToAtlasClass::Convert(ConvertToAtlasClass &cta) {cta.convertDo();}
  131. void ConvertToAtlasClass::PackMaterial(Mtrl &mtrl, MtrlImages &atlas, int thread_index) {mtrl.pack(atlas);}
  132. void ConvertToAtlasClass::setErrors(Mesh &mesh, C Memc<UID> &mtrl_ids, C UID &mesh_id)
  133. {
  134. const Rect tex_rect(0, 1);
  135. REPD(l, mesh.lods())
  136. {
  137. MeshLod &lod=mesh.lod(l); REPA(lod)
  138. {
  139. MeshPart &part=lod.parts[i];
  140. MeshBase &base=part.base; if(base.vtx.tex0())REP(4)
  141. {
  142. UID mtrl_id=part.multiMaterial(i).id(); if(mtrl_id.valid() && mtrl_ids.binaryHas(mtrl_id, Compare))
  143. {
  144. base.explodeVtxs().fixTexOffset();
  145. flt error=0; REPA(base.vtx)MAX(error, Dist(base.vtx.tex0(i), tex_rect)); // verify that all tex uv's are within allowed tex rect
  146. if( error>0.01f) // allow some tolerance
  147. {
  148. Error *e=null; REPA(errors){Error &err=errors[i]; if(err.mesh_id==mesh_id && err.mtrl_id==err.mtrl_id){e=&err; break;}}
  149. if(!e){e=&errors.New(); e->mesh_id=mesh_id; e->mtrl_id=mtrl_id;}
  150. MAX(e->error, error);
  151. }
  152. break;
  153. }
  154. }
  155. }
  156. }
  157. }
  158. bool ConvertToAtlasClass::Create(int &occurence, C UID &id, ptr user) {occurence=0; return true;}
  159. void ConvertToAtlasClass::convertMeshes(C UID &atlas_id)
  160. {
  161. MaterialPtr atlas_mtrl=Proj.gamePath(atlas_id);
  162. Memc<UID> mtrl_ids, obj_ids; REPA(mtrls)mtrl_ids.binaryInclude(mtrls[i].id, Compare);
  163. REPA(Proj.elms) // iterate all elements
  164. {
  165. C Elm &obj=Proj.elms[i]; if(C ElmObj *obj_data=obj.objData()) // if that's an object
  166. if(C Elm *mesh=Proj.findElm(obj_data->mesh_id))if(C ElmMesh *mesh_data=mesh->meshData())
  167. {
  168. REPA(mesh_data->mtrl_ids)if(mtrl_ids.binaryHas(mesh_data->mtrl_ids[i], Compare)) // if that mesh has one of the materials
  169. {
  170. obj_ids.binaryInclude(obj.id, Compare);
  171. break;
  172. }
  173. }
  174. }
  175. Memc<UID> duplicated; Proj.duplicate(obj_ids, duplicated, (mode==NEW) ? " (Atlas)" : " (Atlas Backup)");
  176. // obj_ids duplicated
  177. // NEW atlas
  178. // REPLACE backup
  179. if(mode==NEW)Swap(duplicated, obj_ids);
  180. Memc<UID> &atlased=obj_ids, &non_atlased=duplicated;
  181. REPA(atlased)if(Elm *obj=Proj.findElm(atlased[i]))if(ElmObj *obj_data=obj->objData())
  182. if(Elm *mesh_elm=Proj.findElm(obj_data->mesh_id, ELM_MESH))if(ElmMesh *mesh_data=mesh_elm->meshData())
  183. {
  184. if(ObjEdit.mesh_elm==mesh_elm)ObjEdit.flushMeshSkel();
  185. Mesh mesh; if(Load(mesh, Proj.editPath(mesh_elm->id), Proj.game_path))
  186. {
  187. REPD(l, mesh.lods())
  188. {
  189. MeshLod &lod=mesh.lod(l); REPA(lod)
  190. {
  191. MeshPart &part=lod.parts[i];
  192. Mtrl *mtrl=null;
  193. MaterialPtr mtrls[4]; REPA(mtrls)
  194. {
  195. MaterialPtr &mtrl_ptr=mtrls[i]; if(mtrl_ptr=part.multiMaterial(i))
  196. {
  197. UID mtrl_id=mtrl_ptr.id(); REPA(T.mtrls)if(T.mtrls[i].id==mtrl_id){mtrl=&T.mtrls[i]; mtrl_ptr=atlas_mtrl; break;}
  198. }
  199. }
  200. if(mtrl)
  201. {
  202. part.multiMaterial(mtrls[0], mtrls[1], mtrls[2], mtrls[3]);
  203. MeshBase &base=part.base; if(base.vtx.tex0())
  204. {
  205. Vec2 mul=Vec2(mtrl->packed_rect.size())/tex_size,
  206. add=Vec2(mtrl->packed_rect.min )/tex_size;
  207. base.explodeVtxs().fixTexOffset();
  208. REPA(base.vtx)
  209. {
  210. Vec2 &t=base.vtx.tex0(i);
  211. Clamp(t.x, 0, 1);
  212. Clamp(t.y, 0, 1);
  213. if(mtrl->rotated)Swap(t.x, t.y);
  214. t*=mul; t+=add;
  215. }
  216. base.weldVtx(VTX_ALL, EPSD, EPS_COL_COS, -1); // use small epsilon in case mesh is scaled down, do not remove degenerate faces because they're not needed because we're doing this only because of 'explodeVtxs'
  217. }
  218. }
  219. }
  220. }
  221. mesh_data->newVer(); mesh_data->file_time.getUTC();
  222. Save(mesh, Proj.editPath(mesh_elm->id), Proj.game_path);
  223. Proj.makeGameVer(*mesh_elm);
  224. Server.setElmLong(mesh_elm->id);
  225. if(ObjEdit.mesh_elm==mesh_elm)ObjEdit.reloadMeshSkel();
  226. }
  227. }
  228. // adjust parents, publishing and removed status
  229. TimeStamp time; time.getUTC();
  230. if(atlased.elms()==1) // if there's only one object created, then put the atlas inside of it
  231. if(Elm *atlas=Proj.findElm(atlas_id))
  232. {
  233. atlas->setParent(atlased[0], time); Server.setElmParent(*atlas);
  234. }
  235. // process objects
  236. if(non_atlased.elms()==atlased.elms())
  237. {
  238. REPA(non_atlased)if(Elm *elm=Proj.findElm(non_atlased[i]))
  239. {
  240. if(mode==REPLACE_NO_PUBLISH || mode==REPLACE_REMOVE){elm->setParent(atlased[i], time); Server.setElmParent(*elm);} // move 'non_atlased' to 'atlased'
  241. if(mode==REPLACE_NO_PUBLISH)elm->setNoPublish(true, time);else
  242. if(mode==REPLACE_REMOVE )elm->setRemoved (true, time);
  243. }
  244. if(mode==REPLACE_NO_PUBLISH)Server.noPublishElms(non_atlased, true, time);else
  245. if(mode==REPLACE_REMOVE )Server.removeElms (non_atlased, true, time);
  246. /*if(mode==NEW_NO_PUBLISH || mode==NEW_REMOVE) // move sub elements from 'non_atlased' -> 'atlased'
  247. {
  248. REPA(Proj.elms)
  249. {
  250. Elm &sub=Proj.elms[i]; int index;
  251. if(non_atlased.binarySearch(sub.parent_id, index, Compare) // if is located in one of 'non_atlased'
  252. && !mtrl_ids.binaryHas(sub.id, Compare) // this is not one of the original materials (let's keep original materials in objects)
  253. && ElmVisible(sub.type)) // this is not one of the special element of original object (mesh/skel/phys)
  254. {
  255. sub.setParent(atlased[index], time); Server.setElmParent(sub);
  256. }
  257. }
  258. }*/
  259. if(mode!=NEW) // move materials to old ones, from 'atlased' -> 'non_atlased'
  260. {
  261. REPA(mtrl_ids)if(Elm *mtrl=Proj.findElm(mtrl_ids[i]))
  262. {
  263. int index; if(atlased.binarySearch(mtrl->parent_id, index, Compare)) // if is located in one of 'atlased'
  264. {
  265. mtrl->setParent(non_atlased[index], time); Server.setElmParent(*mtrl);
  266. }
  267. }
  268. }
  269. }
  270. // process materials
  271. REPA(mtrl_ids)if(Elm *elm=Proj.findElm(mtrl_ids[i]))
  272. {
  273. if(mode==REPLACE_NO_PUBLISH)elm->setNoPublish(true, time);else
  274. if(mode==REPLACE_REMOVE )elm->setRemoved (true, time);
  275. }
  276. if(mode==REPLACE_NO_PUBLISH)Server.noPublishElms(mtrl_ids, true, time);else
  277. if(mode==REPLACE_REMOVE )Server.removeElms (mtrl_ids, true, time);
  278. if(mode==REPLACE_NO_PUBLISH || mode==REPLACE_REMOVE)REPA(Proj.elms) // move material sub elements to atlas
  279. {
  280. Elm &sub=Proj.elms[i]; if(mtrl_ids.binaryHas(sub.parent_id, Compare))
  281. {
  282. sub.setParent(atlas_id, time); Server.setElmParent(sub);
  283. }
  284. }
  285. }
  286. void ConvertToAtlasClass::AddMap(Str &dest, C Str &src, C Mtrl &mtrl, bool normal, C Vec &mul)
  287. {
  288. if(src.is())
  289. {
  290. Mems<Edit::FileParams> fps=Edit::FileParams::Decode(dest);
  291. Edit::FileParams &fp=fps.New(); fp=src;
  292. VecI2 size=mtrl.packed_rect.size();
  293. if(mtrl.edit.flip_normal_y && normal) fp.params.New().set("inverseG"); // !! this needs to be done before 'swapRG' !!
  294. if(mtrl.rotated ){fp.params.New().set("swapXY"); if(normal)fp.params.New().set("swapRG");} // !! this needs to be done before 'resize' !!
  295. if(!Equal(mul, Vec(1)) ) fp.params.New().set(normal ? "scaleXY" : "mulRGB", TextVecEx(mul));
  296. fp.params.New().set("resizeClamp", VecI2AsText(size));
  297. if(mtrl.packed_rect.min.any() ) fp.params.New().set("position" , S+mtrl.packed_rect.min.x+','+mtrl.packed_rect.min.y);
  298. dest=Edit::FileParams::Encode(fps);
  299. }
  300. }
  301. void ConvertToAtlasClass::checkSide(Str &dest, int filled)
  302. {
  303. if(dest.is() && !filled) // if there's at least one texture, but it doesn't fill the target fully, then we need to force the size
  304. {
  305. Mems<Edit::FileParams> fps=Edit::FileParams::Decode(dest);
  306. Edit::FileParams &fp=fps.New(); // insert a dummy empty source
  307. fp.params.New().set("position", S+tex_size.x+','+tex_size.y); // with only position specified to force the entire texture size
  308. dest=Edit::FileParams::Encode(fps);
  309. }
  310. }
  311. void ConvertToAtlasClass::convertPerform()
  312. {
  313. // convert before hiding because that may release resources
  314. if(mtrls.elms())
  315. {
  316. MtrlImages atlas_images; if(atlas_images.create(tex_size))
  317. {
  318. Map<UID, int> parent_occurence(Compare, Create); // this is to calculate which parent is most frequently used for stored materials
  319. REPA(mtrls)
  320. {
  321. Mtrl &mtrl=mtrls[i];
  322. MtrlEdit.flush(mtrl.id);
  323. if(Elm *elm=Proj.findElm(mtrl.id))(*parent_occurence(elm->parent_id))++;
  324. }
  325. int occurences=0; UID parent_id=UIDZero; REPA(parent_occurence)if(parent_occurence[i]>occurences){occurences=parent_occurence[i]; parent_id=parent_occurence.key(i);}
  326. atlas_images.clear();
  327. WorkerThreads.process1(mtrls, PackMaterial, atlas_images);
  328. atlas_images.compact();
  329. //atlas_images.Export("d:/", "bmp");
  330. ImporterClass::Import::MaterialEx atlas;
  331. uint tex=CreateBaseTextures(atlas.base_0, atlas.base_1, atlas_images.color, atlas_images.alpha, atlas_images.bump, atlas_images.normal, atlas_images.specular, atlas_images.glow);
  332. VecI2 tex_filled=0; // x=which textures are filled in X, y=which textures are filled in Y
  333. atlas.name="Atlas";
  334. atlas.mtrl.cull=true;
  335. atlas.mtrl.color=0;
  336. atlas.mtrl.ambient=0;
  337. atlas.mtrl.sss=0;
  338. atlas.mtrl.glow=0;
  339. atlas.mtrl.rough=0;
  340. atlas.mtrl.bump=0;
  341. atlas.mtrl.specular=0;
  342. atlas.mtrl.reflect=0;
  343. bool has_alpha_map=false; flt alpha=0; int alpha_num=0; MATERIAL_TECHNIQUE tech=MTECH_DEFAULT; // parameters for alpha materials
  344. REPA(mtrls)if(mtrls[i].edit.alpha_map.is()){has_alpha_map=true; break;} // if at least one 'alpha_map' is specified
  345. FREPA(mtrls)
  346. {
  347. Mtrl &mtrl=mtrls[i];
  348. atlas.mtrl.cull &=mtrl.edit.cull; // if at least one material requires cull disabled, then disable for all
  349. atlas.mtrl.color +=mtrl.edit.color;
  350. atlas.mtrl.ambient +=mtrl.edit.ambient;
  351. atlas.mtrl.sss +=mtrl.edit.sss;
  352. atlas.mtrl.glow +=mtrl.edit.glow;
  353. atlas.mtrl.rough +=mtrl.edit.rough;
  354. atlas.mtrl.bump +=mtrl.edit.bump;
  355. atlas.mtrl.specular+=mtrl.edit.specular;
  356. atlas.mtrl.reflect +=mtrl.edit.reflection;
  357. if(mtrl.edit.tech){alpha+=mtrl.edit.color.w; alpha_num++; tech=mtrl.edit.tech;}
  358. Str alpha_map=mtrl.edit.alpha_map;
  359. if(has_alpha_map) // if at least one material has 'alpha_map', then we need to specify all of them, in case: alpha in one material comes from 'color_map', or it will in the future
  360. if(!alpha_map.is()) // if not yet specified
  361. {
  362. alpha_map=mtrl.edit.color_map; // set from 'color_map'
  363. SetTransform(alpha_map, "channel", "a"); // use alpha channel of 'color_map'
  364. }
  365. AddMap(atlas. color_map, mtrl.edit. color_map, mtrl, false, mtrl.edit.color.xyz);
  366. AddMap(atlas. alpha_map, alpha_map, mtrl);
  367. AddMap(atlas. bump_map, mtrl.edit. bump_map, mtrl);
  368. AddMap(atlas. normal_map, mtrl.edit. normal_map, mtrl, true , mtrl.edit.rough );
  369. AddMap(atlas.specular_map, mtrl.edit.specular_map, mtrl, false, mtrl.edit.specular);
  370. AddMap(atlas. glow_map, mtrl.edit. glow_map, mtrl, false, mtrl.edit.glow );
  371. if(mtrl.packed_rect.includesX(tex_size.x)) // if this packed rect includes the right side
  372. {
  373. if(mtrl.edit. color_map.is())tex_filled.x|=BT_COLOR;
  374. if( alpha_map.is())tex_filled.x|=BT_ALPHA;
  375. if(mtrl.edit. bump_map.is())tex_filled.x|=BT_BUMP;
  376. if(mtrl.edit. normal_map.is())tex_filled.x|=BT_NORMAL;
  377. if(mtrl.edit.specular_map.is())tex_filled.x|=BT_SPECULAR;
  378. if(mtrl.edit. glow_map.is())tex_filled.x|=BT_GLOW;
  379. }
  380. if(mtrl.packed_rect.includesY(tex_size.y)) // if this packed rect includes the bottom side
  381. {
  382. if(mtrl.edit. color_map.is())tex_filled.y|=BT_COLOR;
  383. if( alpha_map.is())tex_filled.y|=BT_ALPHA;
  384. if(mtrl.edit. bump_map.is())tex_filled.y|=BT_BUMP;
  385. if(mtrl.edit. normal_map.is())tex_filled.y|=BT_NORMAL;
  386. if(mtrl.edit.specular_map.is())tex_filled.y|=BT_SPECULAR;
  387. if(mtrl.edit. glow_map.is())tex_filled.y|=BT_GLOW;
  388. }
  389. }
  390. tex_filled.x&=tex_filled.y; // we need both sides to be filled
  391. checkSide(atlas. color_map, tex_filled.x&BT_COLOR );
  392. checkSide(atlas. alpha_map, tex_filled.x&BT_ALPHA );
  393. checkSide(atlas. bump_map, tex_filled.x&BT_BUMP );
  394. checkSide(atlas. normal_map, tex_filled.x&BT_NORMAL );
  395. checkSide(atlas.specular_map, tex_filled.x&BT_SPECULAR);
  396. checkSide(atlas. glow_map, tex_filled.x&BT_GLOW );
  397. if(tex&BT_COLOR ){atlas.mtrl.color.xyz= 1; }else atlas.mtrl.color.xyz/=mtrls.elms();
  398. if(tex&BT_ALPHA ){atlas.mtrl.color.w =(alpha_num ? alpha/alpha_num : 1); atlas.mtrl.technique=tech;}else atlas.mtrl.color.w /=mtrls.elms(); // if we ended up having alpha map, then set parameters from alpha materials only
  399. if(tex&BT_BUMP ){ } atlas.mtrl.bump /=mtrls.elms();
  400. if(tex&BT_NORMAL ){atlas.mtrl.rough =1; }else atlas.mtrl.rough /=mtrls.elms(); // if we ended up having normal map, then it means we've used the baked textures, for which we need to set the full rough multiplier
  401. if(tex&BT_SPECULAR){atlas.mtrl.specular =1; }else atlas.mtrl.specular /=mtrls.elms(); // if we ended up having specular map, then it means we've used the baked textures, for which we need to set the full specular multiplier
  402. if(tex&BT_GLOW ){atlas.mtrl.glow =1; }else atlas.mtrl.glow /=mtrls.elms(); // if we ended up having glow map, then it means we've used the baked textures, for which we need to set the full glow multiplier
  403. atlas.mtrl.ambient /=mtrls.elms();
  404. atlas.mtrl.sss /=mtrls.elms();
  405. atlas.mtrl.reflect /=mtrls.elms();
  406. IMAGE_TYPE ct; ImageProps(atlas.base_0, &atlas.base_0_id, &ct, ForceHQMtrlBase0 ? FORCE_HQ : 0); if(Importer.includeTex(atlas.base_0_id))atlas.base_0.copyTry(atlas.base_0, -1, -1, -1, ct, IMAGE_2D, 0, FILTER_BEST, false, false, false, false);
  407. ImageProps(atlas.base_1, &atlas.base_1_id, &ct, ForceHQMtrlBase1 ? FORCE_HQ : 0); if(Importer.includeTex(atlas.base_1_id))atlas.base_1.copyTry(atlas.base_1, -1, -1, -1, ct, IMAGE_2D, 0, FILTER_BEST, false, false, false, true );
  408. UID atlas_id=Proj.newMtrl(atlas, parent_id).id;
  409. Server.setElmFull(atlas_id);
  410. convertMeshes(atlas_id);
  411. Proj.setList();
  412. }
  413. }
  414. warning.hide();
  415. hide();
  416. }
  417. void ConvertToAtlasClass::convertDo()
  418. {
  419. warning.hide();
  420. Memc<UID> mtrl_ids; REPA(mtrls)mtrl_ids.binaryInclude(mtrls[i].id, Compare);
  421. // check if can (meshes need to have non-wrapped UV's)
  422. errors.clear();
  423. REPA(Proj.elms) // iterate all elements
  424. {
  425. Elm &elm=Proj.elms[i]; if(ElmMesh *mesh_data=elm.meshData()) // if that's a mesh
  426. {
  427. REPA(mesh_data->mtrl_ids)if(mtrl_ids.binaryHas(mesh_data->mtrl_ids[i], Compare)) // if that mesh has one of the materials
  428. {
  429. Mesh mesh; if(ObjEdit.mesh_elm==&elm)mesh.create(ObjEdit.mesh);else Load(mesh, Proj.editPath(elm.id), Proj.game_path); // load edit so we can have access to MeshBase
  430. setErrors(mesh, mtrl_ids, elm.id);
  431. break;
  432. }
  433. }
  434. }
  435. if(errors.elms())warning.display();else convertPerform();
  436. }
  437. void ConvertToAtlasClass::clearProj()
  438. {
  439. mtrls.clear();
  440. warning.hide();
  441. }
  442. void ConvertToAtlasClass::create()
  443. {
  444. warning.create();
  445. add("Force Square" , MEMBER(ConvertToAtlasClass, force_square)).changed(Refresh);
  446. add("Auto Stretch" , MEMBER(ConvertToAtlasClass, auto_stretch)).changed(Refresh);
  447. Property &mode=add("Atlased Objects", MEMBER(ConvertToAtlasClass, mode)).setEnum(mode_t, Elms(mode_t)).desc("When creating material atlases, existing object meshes need to have their UV adjusted.\nWith this option you can control if the adjusted objects:\n-Replace the old ones (keeping their Element ID)\nor\n-They are created as new objects (with new Element ID)");
  448. add("Global Scale" , MEMBER(ConvertToAtlasClass, scale)).range(1.0f/8, 8).mouseEditMode(PROP_MOUSE_EDIT_SCALAR).changed(ChangedScale);
  449. Rect r=::PropWin::create("Convert To Atlas"); button[2].func(HideProjAct, SCAST(GuiObj, T)).show(); mode.combobox.resize(Vec2(0.63f, 0));
  450. autoData(this);
  451. T+=region.create(Rect_LU(0.02f, r.min.y-0.02f, 1.70f, 0.7f));
  452. T+=preview.create(Rect_LU(region.rect().ru()+Vec2(0.02f, 0), 0.7f));
  453. Vec2 size(preview.rect().max.x, Max(-region.rect().min.y, -preview.rect().min.y));
  454. rect(Rect_C(0, size+0.02f+defaultInnerPaddingSize()));
  455. T+=columns.create(Vec2(1.07f, region.rect().max.y+ts.size.y*0.8f), "Scale Original Size Scaled Size", &ts);
  456. T+=t_tex_size.create(preview.rect().up()+Vec2(0, 0.04f));
  457. T+=convert.create(Rect_U(clientWidth()/2, -0.05f, 0.3f, 0.055f), "Convert").func(Convert, T);
  458. }
  459. void ConvertToAtlasClass::autoStretch()
  460. {
  461. if(mtrls.elms())
  462. {
  463. RectI r=mtrls.last().packed_rect; REPA(mtrls)r|=mtrls[i].packed_rect; if(r.size().all())
  464. {
  465. if(r.min.any() || r.max!=tex_size)
  466. {
  467. RectI tex_rect(0, tex_size);
  468. Vec2 scale=Vec2(tex_size)/r.size(), offset=-r.min*scale;
  469. REPA(mtrls)
  470. {
  471. RectI &rect=mtrls[i].packed_rect;
  472. rect=Round(Rect(rect)*scale+offset)&tex_rect;
  473. }
  474. }
  475. }
  476. }
  477. // convert to inclusive
  478. REPAO(mtrls).packed_rect.max--;
  479. REPA (mtrls)
  480. {
  481. RectI &r=mtrls[i].packed_rect;
  482. r.min.x= 0; REPAD(j, mtrls)if(j!=i){C RectI &t=mtrls[j].packed_rect; if(Cuts(r, t))r.min.x=t.max.x+1;}
  483. r.max.x=tex_size.x-1; REPAD(j, mtrls)if(j!=i){C RectI &t=mtrls[j].packed_rect; if(Cuts(r, t))r.max.x=t.min.x-1;}
  484. r.min.y= 0; REPAD(j, mtrls)if(j!=i){C RectI &t=mtrls[j].packed_rect; if(Cuts(r, t))r.min.y=t.max.y+1;}
  485. r.max.y=tex_size.y-1; REPAD(j, mtrls)if(j!=i){C RectI &t=mtrls[j].packed_rect; if(Cuts(r, t))r.max.y=t.min.y-1;}
  486. }
  487. // convert to exclusive
  488. REPAO(mtrls).packed_rect.max++;
  489. }
  490. void ConvertToAtlasClass::refresh()
  491. {
  492. flt y=0;
  493. FREPA(mtrls){mtrls[i].posY(y); y-=h;}
  494. Memc<RectSizeAnchor> mtrl_sizes; mtrl_sizes.setNum(mtrls.elms()); REPAO(mtrl_sizes).size=mtrls[i].scaled_size;
  495. Memc<RectI > mtrl_rects; if(PackRectsUnknownLimit(mtrl_sizes, mtrl_rects, tex_size, true, 0, false, true, ConvertToAtlas.force_square))
  496. {
  497. REPA(mtrls)
  498. {
  499. Mtrl &mtrl=mtrls[i];
  500. mtrl.packed_rect=mtrl_rects[i];
  501. mtrl.rotated=((mtrl.packed_rect.w()>mtrl.packed_rect.h())!=(mtrl.scaled_size.x>mtrl.scaled_size.y));
  502. }
  503. if(auto_stretch)autoStretch();
  504. t_tex_size.set(TexSize(tex_size));
  505. }else
  506. {
  507. REPAO(mtrls).packed_rect.zero();
  508. tex_size.zero();
  509. t_tex_size.clear();
  510. }
  511. REPA(mtrls){Mtrl &mtrl=mtrls[i]; mtrl.setScaleText(mtrl.packed_rect.size());}
  512. warning.hide();
  513. }
  514. void ConvertToAtlasClass::addElms(C MemPtr<UID> &elm_ids)
  515. {
  516. REPA(elm_ids)if(Elm *elm=Proj.findElm(elm_ids[i], ELM_MTRL))
  517. {
  518. REPA(mtrls)if(mtrls[i].id==elm->id)goto skip;
  519. {
  520. Mtrl &mtrl=mtrls.New();
  521. mtrl.id=elm->id;
  522. region+=mtrl.del .create(Rect_LU(0, 0, h, h)).setImage("Gui/close.img").func(Del, mtrl).desc("Remove this Material");
  523. region+=mtrl.name.create(Rect_LU(h, 0, 0.9f, h), Proj.elmFullName(elm->id)).disabled(true);
  524. if(ElmMaterial *mtrl_data=elm->mtrlData())
  525. if(mtrl.base_0=Proj.texPath(mtrl_data->base_0_tex))
  526. mtrl.original_size=mtrl.base_0->size();
  527. mtrl.prop.create(S, MemberDesc(MEMBER(Mtrl, scale)).setTextToDataFunc(Scale)).range(1.0f/8, 8).mouseEditMode(PROP_MOUSE_EDIT_SCALAR).autoData(&mtrl).name.text_style=&ts;
  528. region+=mtrl.t_original_size.create(Vec2(mtrl.name.rect().max.x+0.33f, 0-h*0.5f), TexSize(mtrl.original_size));
  529. region+=mtrl.t_scaled_size .create(Vec2(mtrl.name.rect().max.x+0.56f, 0-h*0.5f));
  530. mtrl.prop.addTo(region, Vec2(mtrl.name.rect().max.x+0.04f, 0), 0, h, 0.15f);
  531. mtrl.setScale();
  532. }
  533. skip:;
  534. }
  535. mtrls.sort(CompareData);
  536. refresh();
  537. activate();
  538. }
  539. void ConvertToAtlasClass::setElms(C MemPtr<UID> &elm_ids)
  540. {
  541. mtrls.clear();
  542. addElms(elm_ids);
  543. }
  544. void ConvertToAtlasClass::drag(Memc<UID> &elms, GuiObj *focus_obj, C Vec2 &screen_pos)
  545. {
  546. if(contains(focus_obj))
  547. {
  548. addElms(elms);
  549. elms.clear();
  550. }
  551. }
  552. ConvertToAtlasClass& ConvertToAtlasClass::hide()
  553. {
  554. ::PropWin::hide();
  555. mtrls.clear(); warning.hide(); // release memory, since we're releasing then we also need to hide the warning to prevent from converting on empty materials
  556. return T;
  557. }
  558. ConvertToAtlasClass::ConvertToAtlasClass() : force_square(false), auto_stretch(false), scale(1), mode(NEW), tex_size(0) {}
  559. ConvertToAtlasClass::Mtrl::Mtrl() : id(UIDZero), original_size(0), scaled_size(0), pos(0), packed_rect(0), rotated(false), scale(1) {}
  560. ConvertToAtlasClass::Error::Error() : mesh_id(UIDZero), mtrl_id(UIDZero), error(0) {}
  561. /******************************************************************************/