Mini Map.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. /******************************************************************************/
  2. #include "stdafx.h"
  3. /******************************************************************************/
  4. MiniMapEditor MiniMapEdit;
  5. bool MiniMapBuilding, MiniMapOk;
  6. int MiniMapAreasLeft;
  7. State StateMiniMap(UpdateMiniMap, DrawMiniMap, InitMiniMap, ShutMiniMap);
  8. /******************************************************************************/
  9. bool InitMiniMap()
  10. {
  11. SetKbExclusive();
  12. //Proj.pause(); we're not pausing so the 'MiniMapEdit' doesn't close and unload mini map element/data/game world
  13. Proj.pauseServer(); // instead just pause the server so no changes are made during mini map generation
  14. MiniMapEdit.init();
  15. UpdateProgress.create(Rect_C(0, -0.05f, 1, 0.045f));
  16. MiniMapBuilding=true;
  17. MiniMapOk=false;
  18. MiniMapAreasLeft=Proj.worldAreasToRebuild(&MiniMapEdit.worldID());
  19. return true;
  20. }
  21. void ShutMiniMap()
  22. {
  23. UpdateProgress.del();
  24. //Proj.resume(); we're not resuming
  25. Proj.resumeServer(); // resume server
  26. WindowSetNormal();
  27. WindowFlash();
  28. MiniMapEdit.reloadAreas();
  29. /*if(MiniMapOk)if(MiniMapVer *ver=MiniMapEdit.ver()) // send new mini map to the server only after full completion (this is not needed since we're resuming the server above)
  30. {
  31. Server.setMiniMapSettings(MiniMapEdit.elm_id, ver.settings, ver.time);
  32. REPA(ver.images)Synchronizer.setMiniMapImage(MiniMapEdit.elm_id, ver.images[i]);
  33. }*/
  34. }
  35. /******************************************************************************/
  36. bool UpdateMiniMap()
  37. {
  38. if(Kb.bp(KB_ESC)){SetProjectState(); Gui.msgBox(S, "Mini Map generation breaked on user request");}
  39. Builder.update(!MiniMapBuilding, &MiniMapEdit.worldID());
  40. if(MiniMapBuilding)
  41. {
  42. UpdateProgress.set(MiniMapAreasLeft-Proj.worldAreasToRebuild(&MiniMapEdit.worldID()), MiniMapAreasLeft);
  43. if(Builder.finished(&MiniMapEdit.worldID()))
  44. {
  45. MiniMapBuilding=false;
  46. WorldEdit.flush(); // flush any world areas that were builded
  47. if(ElmMiniMap *data=MiniMapEdit.data()) // start creating mini maps
  48. {
  49. FDelInside(Proj.gamePath(MiniMapEdit.elm_id));
  50. Proj.createMiniMapPaths(MiniMapEdit.elm_id);
  51. Preview.elmChanged(MiniMapEdit.elm_id);
  52. if(MiniMapVer *ver=MiniMapEdit.ver())
  53. {
  54. data->copyTo(ver->settings);
  55. ver->changed =true;
  56. ver->time .getUTC();
  57. ver->settings.area_size=Game::World.areaSize();
  58. ver->settings.save(Proj.gamePath(MiniMapEdit.elm_id).tailSlash(true)+"Settings");
  59. ver->images .clear();
  60. }
  61. }
  62. Memc<Game::WorldManager::AreaState> areas; Game::World.areaSetState(areas, true); // unload all areas in case they were changed during building
  63. }
  64. Time.wait(1000/30);
  65. }
  66. WindowSetProgress(UpdateProgress());
  67. //Gui.update(); do not update gui as it's not hidden
  68. Server.update(null, true);
  69. if(Ms.bp(3))WindowToggle();
  70. return true;
  71. }
  72. /******************************************************************************/
  73. void DrawMiniMap()
  74. {
  75. D.clear(BackgroundColor());
  76. if(MiniMapBuilding)
  77. {
  78. D.text(0, 0.05f, S+"Waiting for "+Proj.worldAreasToRebuild(&MiniMapEdit.worldID())+" world areas to finish building");
  79. }else
  80. {
  81. for(uint start=Time.curTimeMs(); ; )
  82. {
  83. UpdateProgress.set(MiniMapEdit.progress, MiniMapEdit.totalImages());
  84. WindowSetProgress(UpdateProgress());
  85. if(MiniMapEdit.step())
  86. {
  87. MiniMapEdit.setAreas();
  88. MiniMapEdit.draw(true);
  89. if((Time.curTimeMs()-start)>=1000/30)break;
  90. }else
  91. {
  92. MiniMapOk=true;
  93. SetProjectState();
  94. break;
  95. }
  96. }
  97. D.viewRect(null);
  98. D.text(0, 0.05f, "Creating Mini Map Images");
  99. }
  100. GuiPC gpc;
  101. gpc.visible=gpc.enabled=true;
  102. gpc.client_rect=gpc.clip.set(-D.w(), -D.h(), D.w(), D.h());
  103. gpc.offset.zero();
  104. UpdateProgress.draw(gpc);
  105. D.clip();
  106. //Gui.draw(); do not draw gui as it's not hidden
  107. }
  108. /******************************************************************************/
  109. /******************************************************************************/
  110. cchar8 *MiniMapEditor::ImageSizes[]=
  111. {
  112. "32",
  113. "64",
  114. "128",
  115. "256",
  116. "512",
  117. "1024",
  118. };
  119. cchar8 *MiniMapEditor::AreasPerImage[]=
  120. {
  121. "1",
  122. "2",
  123. "3",
  124. "4",
  125. "5",
  126. "6",
  127. "7",
  128. "8",
  129. "12",
  130. "16",
  131. "24",
  132. };
  133. /******************************************************************************/
  134. void MiniMapEditor::Change::create(ptr user)
  135. {
  136. if(ElmMiniMap *data=MiniMapEdit.data())
  137. {
  138. T.data=*data;
  139. MiniMapEdit.undoVis();
  140. }
  141. }
  142. void MiniMapEditor::Change::apply(ptr user)
  143. {
  144. if(ElmMiniMap *data=MiniMapEdit.data())
  145. {
  146. UID world=data->world_id; int areas=data->areas_per_image;
  147. data->undo(T.data);
  148. MiniMapEdit.setChanged(data->world_id!=world, data->areas_per_image!=areas);
  149. MiniMapEdit.toGui();
  150. MiniMapEdit.undoVis();
  151. }
  152. }
  153. void MiniMapEditor::undoVis() {SetUndo(undos, undo, redo);}
  154. void MiniMapEditor::Render() {MiniMapEdit.render();}
  155. void MiniMapEditor::render() {Game::World.draw();}
  156. int MiniMapEditor::areasPerImage() {ElmMiniMap *data=T.data(); return data ? data->areas_per_image : 4;}
  157. UID MiniMapEditor::worldID() {ElmMiniMap *data=T.data(); return data ? data->world_id : UIDZero;}
  158. flt MiniMapEditor::miniMapWidth() {return Game::World.areaSize()*areasPerImage();}
  159. int MiniMapEditor::totalImages() {return (images.w()+1)*(images.h()+1);}
  160. flt MiniMapEditor::ShadowStep(int i, int num)
  161. {
  162. flt f=flt(i)/num; // uniform fraction
  163. return Lerp(f, 0.5f, 0.97f); // because we'll set the camera so that most objects/heightmaps will be in the center of the viewport range, then move shadow split focus into the center (0.5)
  164. }
  165. void MiniMapEditor::Draw(Viewport &viewport) {MiniMapEdit.draw();}
  166. void MiniMapEditor::draw(bool final)
  167. {
  168. if(ElmMiniMap *data=T.data())
  169. {
  170. // environment
  171. UID env_id=data->env_id; if(!env_id.valid())if(Elm *world=Proj.findElm(data->world_id))if(ElmWorld *world_data=world->worldData())env_id=world_data->env_id;
  172. if(!env_id.valid())CurrentEnvironment().set();else{env=Proj.gamePath(env_id); env->set();}
  173. // graphics settings
  174. MOTION_MODE motion =D. motionMode(); D. motionMode (MOTION_NONE );
  175. AMBIENT_MODE ambient =D. ambientMode(); D.ambientMode (AMBIENT_ULTRA );
  176. int ambient_res=D. ambientRes(); D.ambientRes (0 );
  177. bool bloom_half =D. bloomHalf(); D.bloomHalf (true );
  178. EDGE_SOFTEN_MODE edge =D. edgeSoften(); D.edgeSoften (EDGE_SOFTEN_SMAA);
  179. DOF_MODE dof =D. dofMode(); D. dofMode (DOF_NONE );
  180. flt lod_fac =D. lodFactor(); D. lodFactor (0 );
  181. byte shd_soft =D. shadowSoft(); D.shadowSoft (1);
  182. byte shd_num =D. shadowMapNum(); D.shadowMapNum (6);
  183. bool shd_jitter =D. shadowJitter(); D.shadowJitter (false);
  184. bool eye_adapt =D.eyeAdaptation(); D.eyeAdaptation(false);
  185. bool ocean =Water.draw ; Water.draw =false;
  186. flt view_from =D.viewFrom(), view_range=D.viewRange(), view_fov=D.viewFov();
  187. flt (*shd_step)(int i, int num)=D.shadow_step; D.shadow_step=ShadowStep;
  188. RectI viewport; if(final)viewport.set(0, 0, D.resW(), D.resH());else viewport=D.screenToPixelI(D.viewRect());
  189. int border =pixel_border,
  190. res =data->image_size, // image resolution
  191. res_border=res+border*2; // resolution with borders applied
  192. MIN(res_border, viewport.size().min()); // do not exceed available viewport resolution
  193. res =res_border-border*2; // recalculate resolution without borders
  194. if(final)res =FloorPow2(res); // adjust it to be a power of 2
  195. res_border=res+border*2; // recalculate resolution with borders
  196. bool downsample=false;
  197. if(viewport.w()>=res_border*2
  198. && viewport.h()>=res_border*2 && final){res*=2; res_border*=2; border*=2; downsample=true;} // if viewport resolution is twice as big as rendering resolution, then render everything in 2x bigger size and later downsample for better quality
  199. if(final)D.viewForceSquarePixel(true);
  200. D.viewFov (DegToRad(0.4f), FOV_XY); // set low 'fov' to simulate orthogonal rendering, use 'FOV_XY' to set equal horizontal and vertical fov values to generate uniform scale textures
  201. D.viewRect(final ? &RectI(0, 0, res_border, res_border) : &RectI(viewport.centerI()).extend(res_border/2)); // set viewport to cover image map resolution with borders
  202. // set camera
  203. Cam.at = ((Vec2(image_pos)+0.5f)*miniMapWidth()).x0y(); // set camera at center of current map image
  204. Cam.yaw = 0;
  205. Cam.pitch=-PI_2;
  206. Cam.roll = 0;
  207. Cam.dist = miniMapWidth()*0.5f/Tan(D.viewFovX()/2); // set distance to cover the selected areas
  208. Cam.dist*=flt(res_border)/res; // adjust for borders
  209. Cam.setSpherical().set();
  210. // update viewport
  211. D.viewFrom (ActiveCam.dist*0.01f) // set high 'from' to reduce depth buffer precision issues
  212. .viewRange(ActiveCam.dist*2 ); // set viewport range to cover the range from sky to ground and ground to underground
  213. Renderer(MiniMapEditor::Render);
  214. if(!final)D.pixelToScreen(RectI(viewport.centerI()).extend(res/2)).draw(RED, false);
  215. D. lodFactor (lod_fac);
  216. D. dofMode (dof );
  217. D. motionMode (motion );
  218. D.ambientMode (ambient).ambientRes(ambient_res);
  219. D.bloomHalf (bloom_half);
  220. D.shadowSoft (shd_soft).shadowMapNum(shd_num).shadowJitter(shd_jitter).shadow_step=shd_step;
  221. D.edgeSoften (edge );
  222. D.eyeAdaptation(eye_adapt);
  223. D.viewForceSquarePixel(false).viewFrom(view_from).viewRange(view_range).viewFov(view_fov);
  224. Water.draw =ocean;
  225. if(final)
  226. {
  227. Image image; if(!Renderer.capture(image, -1, -1, IMAGE_R8G8B8, IMAGE_SOFT)){Gui.msgBox(S, "Error capturing screen data"); SetProjectState();}else
  228. {
  229. image.crop(image, border, border, res, res);
  230. if(downsample)image.downSample();
  231. image.copyTry(image, -1, -1, -1, IMAGE_BC1, IMAGE_2D, 1);
  232. image.save(Proj.gamePath(MiniMapEdit.elm_id).tailSlash(true)+image_pos);
  233. if(MiniMapVer *ver=T.ver())
  234. {
  235. ver->images.binaryInclude(image_pos, Compare);
  236. ver->changed=true;
  237. }
  238. }
  239. }
  240. }
  241. }
  242. void MiniMapEditor::Make(MiniMapEditor &mme) {mme.makeDo();}
  243. void MiniMapEditor::PreChanged(C Property &prop) {MiniMapEdit.undos.set(&prop);}
  244. void MiniMapEditor::Changed(C Property &prop) {MiniMapEdit.setChanged();}
  245. void MiniMapEditor::World( MiniMapEditor &mme, C Str &text) {if(ElmMiniMap *data=mme.data()){data->world_id=Proj.findElmID(text, ELM_WORLD); data->world_time.getUTC(); mme.reloadWorld();}}
  246. Str MiniMapEditor::World(C MiniMapEditor &mme ) {if(ElmMiniMap *data=mme.data())return Proj.elmFullName(data->world_id); return S;}
  247. void MiniMapEditor::Env( MiniMapEditor &mme, C Str &text) {if(ElmMiniMap *data=mme.data()){data->env_id=Proj.findElmID(text, ELM_ENV); data->env_time.getUTC();}}
  248. Str MiniMapEditor::Env(C MiniMapEditor &mme ) {if(ElmMiniMap *data=mme.data())return Proj.elmFullName(data->env_id); return S;}
  249. void MiniMapEditor::ArPerImg( MiniMapEditor &mme, C Str &text) {if(ElmMiniMap *data=mme.data()){int i=TextInt(text); data->areas_per_image=(InRange(i, AreasPerImage) ? TextInt(AreasPerImage[i]) : 4); data->areas_per_image_time.getUTC(); mme.reloadAreas();}}
  250. Str MiniMapEditor::ArPerImg(C MiniMapEditor &mme ) {int nearest=-1, dist; if(ElmMiniMap *data=mme.data())REPA(AreasPerImage){int d=Abs(data->areas_per_image-TextInt(AreasPerImage[i])); if(nearest<0 || d<dist){nearest=i; dist=d;}} return nearest;}
  251. void MiniMapEditor::Size( MiniMapEditor &mme, C Str &text) {if(ElmMiniMap *data=mme.data()){int i=TextInt(text); data->image_size=(InRange(i, ImageSizes) ? TextInt(ImageSizes[i]) : 256); data->image_size_time.getUTC();}}
  252. Str MiniMapEditor::Size(C MiniMapEditor &mme ) {int nearest=-1, dist; if(ElmMiniMap *data=mme.data())REPA(ImageSizes){int d=Abs(data->image_size-TextInt(ImageSizes[i])); if(nearest<0 || d<dist){nearest=i; dist=d;}} return nearest;}
  253. void MiniMapEditor::Undo(MiniMapEditor &editor) {editor.undos.undo();}
  254. void MiniMapEditor::Redo(MiniMapEditor &editor) {editor.undos.redo();}
  255. void MiniMapEditor::Locate(MiniMapEditor &editor) {Proj.elmLocate(editor.elm_id);}
  256. ElmMiniMap* MiniMapEditor::data()C {return elm ? elm->miniMapData ( ) : null;}
  257. MiniMapVer* MiniMapEditor::ver()C {return elm ? Proj.miniMapVerRequire(elm->id) : null;}
  258. void MiniMapEditor::makeDo()
  259. {
  260. if(elm)if(ElmMiniMap *data=elm->miniMapData())StateMiniMap.set(StateFadeTime);
  261. }
  262. void MiniMapEditor::init(bool center)
  263. {
  264. ElmMiniMap *data =T.data();
  265. RectI world_areas=Proj.getWorldAreas(worldID()); if(center)world_areas=world_areas.centerI();
  266. int api =areasPerImage();
  267. progress=0;
  268. images.set(DivFloor(world_areas.min.x, api),
  269. DivFloor(world_areas.min.y, api),
  270. DivFloor(world_areas.max.x, api),
  271. DivFloor(world_areas.max.y, api));
  272. image_pos=images.min;
  273. image_pos.x--; // go back 1 step
  274. }
  275. bool MiniMapEditor::step()
  276. {
  277. if(image_pos.y>images.max.y)return false; // if out of Y range then it means that we've processed all mini maps
  278. // update mini map positions
  279. progress++;
  280. image_pos.x++; // move 1 step forward
  281. if(image_pos.x>images.max.x) // if out of X range, then reset X position and proceed to the next Y position
  282. {
  283. image_pos.x=images.min.x; // reset X
  284. image_pos.y++; // proceed to next Y
  285. if(image_pos.y>images.max.y)return false; // if out of Y range then it means that we've processed all mini maps
  286. }
  287. return true;
  288. }
  289. void MiniMapEditor::create()
  290. {
  291. add("World" , MemberDesc(DATA_STR).setFunc(World , World )).elmType(ELM_WORLD);
  292. add("Environment" , MemberDesc(DATA_STR).setFunc(Env , Env )).elmType(ELM_ENV );
  293. add("Areas Per Image", MemberDesc( ).setFunc(ArPerImg, ArPerImg)).setEnum(AreasPerImage, Elms(AreasPerImage));
  294. add("Image Size" , MemberDesc( ).setFunc(Size , Size )).setEnum(ImageSizes , Elms(ImageSizes ));
  295. autoData(this);
  296. flt h=0.043f;
  297. Rect r=::PropWin::create("Mini Map Editor", Vec2(0.02f, -0.07f), 0.036f, h, PropElmNameWidth); ::PropWin::changed(Changed, PreChanged);
  298. T+=undo .create(Rect_LU(0.02f, -0.01f , 0.05f, 0.05f)).func(Undo, T).focusable(false).desc("Undo"); undo.image="Gui/Misc/undo.img";
  299. T+=redo .create(Rect_LU(undo.rect().ru(), 0.05f, 0.05f)).func(Redo, T).focusable(false).desc("Redo"); redo.image="Gui/Misc/redo.img";
  300. T+=locate.create(Rect_LU(redo.rect().ru()+Vec2(0.01f, 0), 0.14f, 0.05f), "Locate").func(Locate, T).focusable(false).desc("Locate this element in the Project");
  301. prop_max_x=r.max.x; button[1].show(); button[2].func(HideProjAct, SCAST(GuiObj, T)).show(); flag|=WIN_RESIZABLE;
  302. T+=make.create(Rect_U(r.down()-Vec2(0, 0.04f), 0.3f, 0.055f), "Create").func(Make, T).desc("Create mini map using current settings");
  303. T+=viewport.create(Draw);
  304. rect(Rect_C(0, 0, Min(1.7f, D.w()*2), Min(1.12f, D.h()*2)));
  305. }
  306. Rect MiniMapEditor::sizeLimit()C {Rect r=::EE::Window::sizeLimit(); r.min.set(0.9f, 0.5f); return r;}
  307. MiniMapEditor& MiniMapEditor::rect(C Rect &rect)
  308. {
  309. ::EE::Window::rect(rect);
  310. viewport.rect(Rect(prop_max_x, -clientHeight(), clientWidth(), 0).extend(-0.02f));
  311. return T;
  312. }
  313. void MiniMapEditor::flush()
  314. {
  315. if(elm && changed)
  316. {
  317. if(ElmMiniMap *data=elm->miniMapData())data->newVer(); // modify just before saving/sending in case we've received data from server after edit
  318. Server.setElmShort(elm->id);
  319. }
  320. changed=false;
  321. }
  322. void MiniMapEditor::setAreas(int border)
  323. {
  324. Memc<Game::WorldManager::AreaState> areas;
  325. RectI recti=image_pos; recti.max++; recti*=areasPerImage(); recti.max--;
  326. recti.extend(border); // for nearby shadow casting objects
  327. for(int y=recti.min.y; y<=recti.max.y; y++)
  328. for(int x=recti.min.x; x<=recti.max.x; x++)areas.New().set(VecI2(x, y), Game::AREA_ACTIVE);
  329. DataPath(Proj.game_path);
  330. Game::World.areaSetState(areas, true);
  331. DataPath(S);
  332. }
  333. void MiniMapEditor::reloadAreas()
  334. {
  335. if(ElmMiniMap *data=T.data())
  336. {
  337. if(data->world_id==WorldEdit.elm_id)WorldEdit.flush();
  338. init(true); step();
  339. setAreas(1); // use low border because this is just a preview
  340. Time.skipUpdate();
  341. }
  342. }
  343. void MiniMapEditor::reloadWorld()
  344. {
  345. if(elm)
  346. {
  347. if(ElmMiniMap *data=T.data())
  348. {
  349. InitGameObjContainers();
  350. Game::World.use_background_loading=false; // this must be disabled because we may write to world on the main thread
  351. DataPath(Proj.game_path);
  352. Game::World.mode(Game::WORLD_MANUAL).NewTry(Proj.gamePath(data->world_id));
  353. DataPath(S);
  354. reloadAreas();
  355. }
  356. }else
  357. {
  358. Game::World.del();
  359. Game::World.use_background_loading=true;
  360. }
  361. }
  362. void MiniMapEditor::setChanged(bool world, bool areas)
  363. {
  364. if(elm)
  365. {
  366. changed=true;
  367. if(ElmMiniMap *data=elm->miniMapData())data->newVer();
  368. if(world)reloadWorld();else if(areas)reloadAreas();
  369. toGui();
  370. }
  371. }
  372. void MiniMapEditor::set(Elm *elm)
  373. {
  374. if(elm && elm->type!=ELM_MINI_MAP)elm=null;
  375. if(T.elm!=elm)
  376. {
  377. flush();
  378. undos.del(); undoVis();
  379. T.elm =elm;
  380. T.elm_id=(elm ? elm->id : UIDZero);
  381. toGui();
  382. Proj.refresh(false, false);
  383. visible(T.elm!=null).moveToTop();
  384. reloadWorld();
  385. }
  386. }
  387. void MiniMapEditor::activate(Elm *elm) {set(elm); if(T.elm)::EE::GuiObj::activate();}
  388. void MiniMapEditor::toggle(Elm *elm) {if(elm==T.elm)elm=null; set(elm);}
  389. MiniMapEditor& MiniMapEditor::hide( ){if(visible()){::PropWin::hide(); set(null);} return T;}
  390. void MiniMapEditor::elmChanged(C UID &elm_id)
  391. {
  392. if(elm && elm->id==elm_id)
  393. {
  394. undos.set(null, true);
  395. reloadWorld();
  396. toGui();
  397. }
  398. }
  399. void MiniMapEditor::erasing(C UID &elm_id) {if(elm && elm->id==elm_id)set(null);}
  400. MiniMapEditor::MiniMapEditor() : pixel_border(32), prop_max_x(0), elm_id(UIDZero), elm(null), changed(false), progress(0), image_pos(0), images(0), undos(true) {}
  401. /******************************************************************************/