LevelRenderer.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. package me.hammerle.supersnuvi.rendering;
  2. import java.util.ArrayList;
  3. import me.hammerle.snuviengine.api.Shader;
  4. import me.hammerle.snuviengine.api.Texture;
  5. import me.hammerle.snuviengine.api.TextureRenderer;
  6. import me.hammerle.snuviengine.util.Rectangle;
  7. import me.hammerle.supersnuvi.Game;
  8. import me.hammerle.supersnuvi.entity.Entity;
  9. import me.hammerle.supersnuvi.gamelogic.Level;
  10. import me.hammerle.supersnuvi.gamelogic.LevelData;
  11. import me.hammerle.supersnuvi.tiles.Tile;
  12. import me.hammerle.supersnuvi.util.Utils;
  13. public class LevelRenderer implements IRenderer<Level>
  14. {
  15. public final static float ERROR = 1f / 65536f;
  16. public final static Texture TILES = new Texture("resources/tiles.png");
  17. private final static Texture GUI = new Texture("resources/gui.png");
  18. private final static TextureRenderer GUI_RENDERER = new TextureRenderer(60);
  19. private final static int MESH_SIZE = 16;
  20. private float cameraX = 0.0f;
  21. private float cameraY = 0.0f;
  22. private Level lastRenderedLevel = null;
  23. private int meshLayers = 0;
  24. private int meshWidth = 0;
  25. private int meshHeight = 0;
  26. private TextureRenderer[][][] meshes = null;
  27. @Override
  28. public void renderTick(Shader sh, float lag, Level level)
  29. {
  30. updateCamera(sh, lag, level);
  31. if(lastRenderedLevel != level)
  32. {
  33. updateMeshes(level);
  34. lastRenderedLevel = level;
  35. for(int i = 0; i < 32; i++)
  36. {
  37. level.getLight(i).markDirty();
  38. }
  39. }
  40. Shader.setAmbientLight(level.getAmbientRed(), level.getAmbientGreen(), level.getAmbientBlue());
  41. Shader.setLightEnabled(true);
  42. for(int i = 0; i < 1; i++)
  43. {
  44. Light l = level.getLight(i);
  45. if(l.isDirty())
  46. {
  47. sh.setLightColor(i, l.getRed(), l.getGreen(), l.getBlue());
  48. sh.setLightStrength(i, l.getStrength());
  49. l.clearDirtyFlag();
  50. }
  51. sh.setLightLocation(i,
  52. Utils.interpolate(l.getLastX(), l.getX(), lag) + cameraX,
  53. Utils.interpolate(l.getLastY(), l.getY(), lag) + cameraY);
  54. }
  55. renderTilesAndEntities(sh, lag, level);
  56. Shader.setLightEnabled(false);
  57. renderOverlay(sh, level);
  58. }
  59. private float getView(int screenSize, int levelSize, float pos)
  60. {
  61. pos -= screenSize >> 1;
  62. if(pos < 0)
  63. {
  64. return 0;
  65. }
  66. float max = levelSize * Tile.SIZE - screenSize;
  67. if(pos > max)
  68. {
  69. return max;
  70. }
  71. return pos;
  72. }
  73. private void updateCamera(Shader sh, float lag, Level level)
  74. {
  75. Entity hero = level.getHero();
  76. int levelWidth = level.getData().getWidth();
  77. int levelHeight = level.getData().getHeight();
  78. int screenWidth = sh.getViewWidth();
  79. int screenHeight = sh.getViewHeight();
  80. float halfHeroWidth = hero.getWidth() * 0.5f;
  81. float halfHeroHeight = hero.getHeight() * 0.5f;
  82. float oldCamX = -getView(screenWidth, levelWidth, hero.getLastX() + halfHeroWidth);
  83. float oldCamY = -getView(screenHeight, levelHeight, hero.getLastY() + halfHeroHeight);
  84. float camX = -getView(screenWidth, levelWidth, hero.getX() + halfHeroWidth);
  85. float camY = -getView(screenHeight, levelHeight, hero.getY() + halfHeroHeight);
  86. cameraX = Utils.interpolate(oldCamX, camX, lag);
  87. cameraY = Utils.interpolate(oldCamY, camY, lag);
  88. sh.translateTo(cameraX, cameraY);
  89. sh.updateMatrix();
  90. }
  91. private void updateMeshes(Level level)
  92. {
  93. if(meshes != null)
  94. {
  95. for(int l = 0; l < meshLayers; l++)
  96. {
  97. for(int mx = 0; mx < meshWidth; mx++)
  98. {
  99. for(int my = 0; my < meshHeight; my++)
  100. {
  101. meshes[l][mx][my].delete();
  102. }
  103. }
  104. }
  105. }
  106. meshLayers = level.getData().getLayers() - 1;
  107. meshWidth = (int) Math.ceil((double) level.getData().getWidth() / MESH_SIZE);
  108. meshHeight = (int) Math.ceil((double) level.getData().getHeight() / MESH_SIZE);
  109. meshes = new TextureRenderer[meshLayers][meshWidth][meshHeight];
  110. for(int l = 0; l < meshLayers; l++)
  111. {
  112. for(int mx = 0; mx < meshWidth; mx++)
  113. {
  114. for(int my = 0; my < meshHeight; my++)
  115. {
  116. meshes[l][mx][my] = new TextureRenderer(MESH_SIZE * MESH_SIZE * 2, false);
  117. }
  118. }
  119. }
  120. }
  121. private void drawMesh(Level level, int l, int tl, int mx, int my)
  122. {
  123. TextureRenderer tr = meshes[l][mx][my];
  124. if(!tr.isBuilt())
  125. {
  126. LevelData data = level.getData();
  127. int tsx = mx * MESH_SIZE;
  128. int tsy = my * MESH_SIZE;
  129. int tex = Math.min(tsx + MESH_SIZE, data.getWidth());
  130. int tey = Math.min(tsy + MESH_SIZE, data.getHeight());
  131. for(int x = tsx; x < tex; x++)
  132. {
  133. for(int y = tsy; y < tey; y++)
  134. {
  135. Tile t = Game.get().getTile(data.getTile(tl, x, y));
  136. if(t.shouldRender(x, y, level))
  137. {
  138. float minX = x * Tile.SIZE + t.getOffsetX();
  139. float minY = y * Tile.SIZE + t.getOffsetY();
  140. tr.addRectangle(minX, minY,
  141. minX + t.getWidth(), minY + t.getHeight(),
  142. t.getTextureMinX(x, y, level) + ERROR, t.getTextureMinY(x, y, level) + ERROR,
  143. t.getTextureMaxX(x, y, level) - ERROR, t.getTextureMaxY(x, y, level) - ERROR);
  144. }
  145. }
  146. }
  147. tr.build();
  148. }
  149. tr.draw();
  150. }
  151. private void renderTilesAndEntities(Shader sh, float lag, Level level)
  152. {
  153. int startX = (int) (-cameraX / (MESH_SIZE * Tile.SIZE));
  154. int startY = (int) (-cameraY / (MESH_SIZE * Tile.SIZE));
  155. int endX = (int) Math.ceil((-cameraX + sh.getViewWidth()) / (MESH_SIZE * Tile.SIZE));
  156. int endY = (int) Math.ceil((-cameraY + sh.getViewHeight()) / (MESH_SIZE * Tile.SIZE));
  157. startX = Math.min(Math.max(startX, 0), meshWidth);
  158. startY = Math.min(Math.max(startY, 0), meshHeight);
  159. endX = Math.min(Math.max(endX, 0), meshWidth);
  160. endY = Math.min(Math.max(endY, 0), meshHeight);
  161. // clear meshes which need an update
  162. if(level.getTileUpdater().shouldUpdateAll())
  163. {
  164. if(meshes != null)
  165. {
  166. for(int l = 0; l < meshLayers; l++)
  167. {
  168. for(int mx = 0; mx < meshWidth; mx++)
  169. {
  170. for(int my = 0; my < meshHeight; my++)
  171. {
  172. meshes[l][mx][my].clear();
  173. }
  174. }
  175. }
  176. }
  177. }
  178. else
  179. {
  180. level.getTileUpdater().forEach((layer, x, y) ->
  181. {
  182. if(layer == level.getData().getBackgroundIndex() + 1)
  183. {
  184. // do not update changes on entity layer
  185. return;
  186. }
  187. if(layer > level.getData().getBackgroundIndex())
  188. {
  189. layer--;
  190. }
  191. meshes[layer][x / MESH_SIZE][y / MESH_SIZE].clear();
  192. });
  193. }
  194. level.getTileUpdater().clear();
  195. // background
  196. sh.setColorEnabled(false);
  197. sh.setTextureEnabled(true);
  198. sh.setBlendingEnabled(true);
  199. TILES.bind();
  200. LevelData data = level.getData();
  201. int fromLayer = 0;
  202. int toLayer = level.getData().getBackgroundIndex() + 1;
  203. for(int l = fromLayer; l < toLayer; l++)
  204. {
  205. for(int mx = startX; mx < endX; mx++)
  206. {
  207. for(int my = startY; my < endY; my++)
  208. {
  209. drawMesh(level, l, l, mx, my);
  210. }
  211. }
  212. }
  213. // entities
  214. level.forEachEntity(entity -> entity.renderTick(lag));
  215. // foreground
  216. sh.setColorEnabled(false);
  217. sh.setTextureEnabled(true);
  218. sh.setBlendingEnabled(true);
  219. TILES.bind();
  220. fromLayer = toLayer + 1;
  221. toLayer = level.getData().getLayers();
  222. for(int l = fromLayer; l < toLayer; l++)
  223. {
  224. for(int mx = startX; mx < endX; mx++)
  225. {
  226. for(int my = startY; my < endY; my++)
  227. {
  228. drawMesh(level, l - 1, l, mx, my);
  229. }
  230. }
  231. }
  232. }
  233. public static String formatBottles(int bottles, int maxBottles)
  234. {
  235. char[] c = new char[5];
  236. if(bottles <= 9)
  237. {
  238. c[0] = '0';
  239. c[1] = (char) (bottles + '0');
  240. }
  241. else if(bottles > 99)
  242. {
  243. c[0] = 'X';
  244. c[1] = 'X';
  245. }
  246. else
  247. {
  248. c[0] = (char) ((bottles / 10) + '0');
  249. c[1] = (char) ((bottles % 10) + '0');
  250. }
  251. c[2] = '/';
  252. int currentMaxSouls = Math.max(bottles, maxBottles);
  253. if(currentMaxSouls <= 9)
  254. {
  255. c[3] = '0';
  256. c[4] = (char) (currentMaxSouls + '0');
  257. }
  258. else if(currentMaxSouls > 99)
  259. {
  260. c[3] = 'X';
  261. c[4] = 'X';
  262. }
  263. else
  264. {
  265. c[3] = (char) ((currentMaxSouls / 10) + '0');
  266. c[4] = (char) ((currentMaxSouls % 10) + '0');
  267. }
  268. return new String(c);
  269. }
  270. public static String formatTime(float time)
  271. {
  272. if(time == -1.0f)
  273. {
  274. return "-----";
  275. }
  276. else if(time >= 999.9f)
  277. {
  278. return "999.9";
  279. }
  280. return String.format("%05.1f", time);
  281. }
  282. private String[] split(String s)
  283. {
  284. ArrayList<String> list = new ArrayList<>();
  285. int old = 0;
  286. int index = 0;
  287. while(index < s.length())
  288. {
  289. switch(s.charAt(index))
  290. {
  291. case '\n':
  292. list.add(s.substring(old, index));
  293. list.add("\n");
  294. old = index + 1;
  295. break;
  296. case ' ':
  297. list.add(s.substring(old, index));
  298. old = index + 1;
  299. break;
  300. }
  301. index++;
  302. }
  303. if(old < s.length())
  304. {
  305. list.add(s.substring(old, index));
  306. }
  307. return list.toArray(new String[list.size()]);
  308. }
  309. private void renderMessage(Shader sh, Level level)
  310. {
  311. String message = level.getMessage();
  312. if(message != null)
  313. {
  314. float lineWidth = sh.getFontRenderer().getWidth();
  315. String[] messageParts = split(message);
  316. int index = 0;
  317. ArrayList<StringBuilder> list = new ArrayList<>();
  318. list.add(new StringBuilder());
  319. float currentWidth = 0;
  320. float w = sh.getViewWidth() - 26;
  321. for(String s : messageParts)
  322. {
  323. if(s.equals("\n"))
  324. {
  325. currentWidth = w;
  326. continue;
  327. }
  328. Rectangle rec = sh.getFontRenderer().getSize(s);
  329. // + lineWidth for the space
  330. if(currentWidth + rec.getWidth() + lineWidth < w)
  331. {
  332. currentWidth += rec.getWidth();
  333. StringBuilder sb = list.get(index);
  334. if(sb.length() == 0)
  335. {
  336. sb.append(s);
  337. }
  338. else
  339. {
  340. sb.append(" ");
  341. sb.append(s);
  342. currentWidth += lineWidth;
  343. }
  344. }
  345. else
  346. {
  347. StringBuilder sb = new StringBuilder();
  348. list.add(sb);
  349. index++;
  350. sb.append(s);
  351. currentWidth = rec.getWidth();
  352. }
  353. }
  354. float height = list.size() * sh.getFontRenderer().getHeight();
  355. sh.setColorEnabled(true);
  356. sh.setTextureEnabled(false);
  357. float messageY = sh.getViewHeight() - height - 26;
  358. sh.getColorRenderer().drawRectangle(0.0f, messageY, sh.getViewWidth(), sh.getViewHeight(), 0x77000000);
  359. messageY += 13;
  360. sh.setTextureEnabled(true);
  361. for(StringBuilder sb : list)
  362. {
  363. messageY = sh.getFontRenderer().drawString(13.0f, messageY, sb.toString());
  364. }
  365. sh.setColorEnabled(false);
  366. }
  367. }
  368. private void renderOverlay(Shader sh, Level level)
  369. {
  370. // menu rendering
  371. sh.translateTo(0.0f, 0.0f);
  372. sh.updateMatrix();
  373. // grey background of clock and bottles
  374. float lineHeight = sh.getFontRenderer().getHeight();
  375. float lineWidth = sh.getFontRenderer().getWidth();
  376. sh.setColorEnabled(true);
  377. sh.setTextureEnabled(false);
  378. sh.getColorRenderer().drawRectangle(0.0f, 0.0f, (lineWidth * 6.0f) + 10.0f, (lineHeight * 2.0f + 10.0f), 0x77000000);
  379. sh.setTextureEnabled(true);
  380. float y = 5.0f;
  381. y = sh.getFontRenderer().drawString(13.0f, y, formatBottles(level.getCurrentBottles(), level.getMaxBottles()));
  382. sh.getFontRenderer().drawString(13.0f, y, formatTime(level.getTime()));
  383. sh.setColorEnabled(false);
  384. // draw messages
  385. renderMessage(sh, level);
  386. GUI.bind();
  387. GUI_RENDERER.clear();
  388. int scale = sh.getViewScale();
  389. // bottles
  390. switch(scale)
  391. {
  392. case 1: GUI_RENDERER.addRectangle(6.0f, 4.0f, 12.0f, 14.0f, 0.0f, 0.046875f, 0.01171875f, 0.068359375f); break;
  393. case 2: GUI_RENDERER.addRectangle(6.0f, 4.0f, 12.0f, 14.0f, 0.01171875f, 0.046875f, 0.037109375f, 0.0859375f); break;
  394. default: GUI_RENDERER.addRectangle(6.0f, 4.0f, 12.0f, 14.0f, 0.037109375f, 0.046875f, 0.06640625f, 0.10546875f); break;
  395. }
  396. // clock
  397. switch(scale)
  398. {
  399. case 1: GUI_RENDERER.addRectangle(4.0f, y, 13.0f, y + 9.0f, 0.0f, 0.265625f, 0.017578125f, 0.283203125f); break;
  400. case 2: GUI_RENDERER.addRectangle(4.5f, y, 13.0f, y + 8.5f, 0.017578125f, 0.265625f, 0.05078125f, 0.298828125f); break;
  401. default: GUI_RENDERER.addRectangle(4.666666666f, y, 13.0f, y + 8.333333333f, 0.05078125f, 0.265625f, 0.099609375f, 0.314453125f); break;
  402. }
  403. float w = sh.getViewWidth();
  404. // gui background
  405. GUI_RENDERER.addRectangle(w - 111.0f, 0.0f, w - 44.0f, 24.0f, 0.0f, 0.0f, 0.130859375f, 0.046875f);
  406. GUI_RENDERER.addRectangle(w - 44.0f, 0.0f, w - 18.0f, 16.0f, 0.130859375f, 0.0f, 0.181640625f, 0.03125f);
  407. GUI_RENDERER.addRectangle(w - 76.0f, 24.0f, w - 45.0f, 57.0f, 0.068359375f, 0.046875f, 0.12890625f, 0.111328125f);
  408. // health mirror
  409. int healthFrame = (int) (level.getHero().getHealth().getHealthPercent() * 7);
  410. float leftMirror = (7 - healthFrame) * 0.0625f;
  411. GUI_RENDERER.addRectangle(w - 39.0f, 8.0f, w - 7.0f, 46.0f, leftMirror, 0.15625f, leftMirror + 0.0625f, 0.23046875f);
  412. // energy
  413. float energy = level.getHero().getEnergy().getEnergyPercent();
  414. float fullEnd = w - 109.0f + 64.0f * energy;
  415. GUI_RENDERER.addRectangle(w - 109.0f, 13.0f, fullEnd, 21.0f, 0.0f, 0.140625f, 0.125f * energy, 0.15625f);
  416. GUI_RENDERER.addRectangle(fullEnd, 13.0f, w - 45.0f, 21.0f, 0.125f * energy, 0.125f, 0.125f, 0.140625f);
  417. // gui foreground
  418. GUI_RENDERER.addRectangle(w - 49.0f, 0.0f, w, 64.0f, 0.201171875f, 0.0f, 0.296875f, 0.125f);
  419. GUI_RENDERER.addRectangle(w - 109.0f, 15.0f, w - 106.0f, 18.0f, 0.15625f, 0.03125f, 0.162109375f, 0.037109375f);
  420. GUI_RENDERER.addRectangle(w - 97.0f, 15.0f, w - 92.0f, 20.0f, 0.1796875f, 0.03125f, 0.189453125f, 0.041015625f);
  421. // health number overlay
  422. GUI_RENDERER.addRectangle(w - 30.0f, 53.0f, w - 12.0f, 62.0f, leftMirror, 0.23828125f, leftMirror + 0.03515625f, 0.255859375f);
  423. GUI_RENDERER.build();
  424. GUI_RENDERER.draw();
  425. // dynamic clock hand
  426. sh.setColorEnabled(true);
  427. sh.setTextureEnabled(false);
  428. switch(scale)
  429. {
  430. case 1: sh.translateTo(8.5f, y + 4.5f); break;
  431. case 2: sh.translateTo(8.75f, y + 4.25f); break;
  432. default: sh.translateTo(8.8333333333f, y + 4.16666667f); break;
  433. }
  434. sh.rotate(-level.getTime() * 72.0f);
  435. sh.updateMatrix();
  436. sh.getColorRenderer().drawRectangle(-0.5f / scale, -0.5f / scale, 0.5f / scale, 4.0f - 0.5f * scale, 0xFF000000);
  437. }
  438. }