Open Source Tomb Raider Engine
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

TextureManager.cpp 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. /*!
  2. * \file src/TextureManager.cpp
  3. * \brief Texture Registry
  4. *
  5. * \author Mongoose
  6. * \author xythobuz
  7. */
  8. #include "imgui/imgui.h"
  9. #include "stb/stb_image.h"
  10. #include "global.h"
  11. #include "Game.h"
  12. #include "Log.h"
  13. #include "RunTime.h"
  14. #include "World.h"
  15. #include "utils/Folder.h"
  16. #include "utils/pcx.h"
  17. #include "utils/pixel.h"
  18. #include "utils/random.h"
  19. #include "utils/strings.h"
  20. #include "TextureManager.h"
  21. #include <glbinding/gl/gl.h>
  22. glm::vec2 TextureTile::getUV(unsigned int i) {
  23. glm::vec2 uv(vertices.at(i).xPixel,
  24. vertices.at(i).yPixel);
  25. /*! \fixme
  26. * This is my somewhat hacky approach to fixing
  27. * the bad texture-bleeding problems everywhere.
  28. * That's better, but makes the seams between
  29. * each sector much more visible!
  30. */
  31. if (vertices.at(i).xCoordinate == 1) {
  32. uv.x += 0.375f;
  33. }
  34. if (vertices.at(i).yCoordinate == 1) {
  35. uv.y += 0.375f;
  36. }
  37. return uv / 256.0f;
  38. }
  39. // ----------------------------------------------------------------------------
  40. #define COLOR_PALETTE_SIZE 256
  41. std::vector<unsigned int> TextureManager::mTextureIdsGame;
  42. std::vector<unsigned int> TextureManager::mTextureIdsSystem;
  43. std::vector<TextureTile*> TextureManager::tiles;
  44. std::vector<std::vector<int>> TextureManager::animations;
  45. std::vector<int> TextureManager::gameUnits;
  46. std::vector<int> TextureManager::systemUnits;
  47. unsigned int TextureManager::nextFreeTextureUnit = 0;
  48. std::vector<BufferManager> TextureManager::gameBuffers;
  49. std::vector<BufferManager> TextureManager::systemBuffers;
  50. std::array<glm::vec4, 256> TextureManager::colorPalette;
  51. std::vector<std::tuple<unsigned char*, unsigned int, unsigned int>> TextureManager::indexedTextures;
  52. int TextureManager::initialize() {
  53. orAssertEqual(mTextureIdsGame.size(), 0);
  54. orAssertEqual(mTextureIdsSystem.size(), 0);
  55. while (mTextureIdsSystem.size() < 2) {
  56. unsigned int id;
  57. gl::glGenTextures(1, &id);
  58. mTextureIdsSystem.push_back(id);
  59. }
  60. unsigned char* image = generateColorTexture(glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), 32, 32, 32);
  61. int res = loadBufferSlot(image, 32, 32, ColorMode::RGBA, 32, TextureStorage::SYSTEM, TEXTURE_WHITE,
  62. false);
  63. delete [] image;
  64. if (res < 0) {
  65. return -1;
  66. }
  67. return 0;
  68. }
  69. int TextureManager::initializeSplash() {
  70. Folder f(RunTime::getPakDir());
  71. std::vector<File> files;
  72. f.findRecursiveFilesEndingWith(files, ".pcx");
  73. f.findRecursiveFilesEndingWith(files, ".bmp");
  74. f.findRecursiveFilesEndingWith(files, ".png");
  75. f.findRecursiveFilesEndingWith(files, ".tga");
  76. f.findRecursiveFilesEndingWith(files, ".jpg");
  77. if (files.size() == 0) {
  78. if (loadImage(RunTime::getDataDir() + "/splash.tga", TextureStorage::SYSTEM, TEXTURE_SPLASH) < 0) {
  79. return -2;
  80. }
  81. } else {
  82. int i = randomInteger(files.size() - 1);
  83. if (loadImage(files.at(i).getPath(), TextureStorage::SYSTEM, TEXTURE_SPLASH) < 0) {
  84. if (loadImage(RunTime::getDataDir() + "/splash.tga", TextureStorage::SYSTEM, TEXTURE_SPLASH) < 0) {
  85. return -3;
  86. }
  87. }
  88. }
  89. return 0;
  90. }
  91. void TextureManager::shutdown() {
  92. while (mTextureIdsSystem.size() > 0) {
  93. unsigned int id = mTextureIdsSystem.at(mTextureIdsSystem.size() - 1);
  94. gl::glDeleteTextures(1, &id);
  95. mTextureIdsSystem.pop_back();
  96. }
  97. gameBuffers.clear();
  98. systemBuffers.clear();
  99. clear();
  100. }
  101. void TextureManager::clear() {
  102. while (mTextureIdsGame.size() > 0) {
  103. unsigned int id = mTextureIdsGame.at(mTextureIdsGame.size() - 1);
  104. gl::glDeleteTextures(1, &id);
  105. mTextureIdsGame.pop_back();
  106. }
  107. while (!tiles.empty()) {
  108. delete tiles.at(tiles.size() - 1);
  109. tiles.pop_back();
  110. }
  111. animations.clear();
  112. gameUnits.clear();
  113. systemUnits.clear();
  114. nextFreeTextureUnit = 0;
  115. indexedTextures.clear();
  116. }
  117. int TextureManager::loadBufferSlot(unsigned char* image,
  118. unsigned int width, unsigned int height,
  119. ColorMode mode, unsigned int bpp,
  120. TextureStorage s, int slot, bool filter) {
  121. orAssertGreaterThan(width, 0);
  122. orAssertGreaterThan(height, 0);
  123. orAssert((mode == ColorMode::RGB)
  124. || (mode == ColorMode::BGR)
  125. || (mode == ColorMode::ARGB)
  126. || (mode == ColorMode::RGBA)
  127. || (mode == ColorMode::BGRA));
  128. orAssert((bpp == 8) || (bpp == 24) || (bpp == 32));
  129. if (slot < 0)
  130. slot = getIds(s).size();
  131. while (getIds(s).size() <= slot) {
  132. unsigned int id;
  133. gl::glGenTextures(1, &id);
  134. getIds(s).push_back(id);
  135. }
  136. gl::GLenum glcMode;
  137. switch (mode) {
  138. case ColorMode::BGR:
  139. glcMode = gl::GL_BGR;
  140. break;
  141. case ColorMode::RGB:
  142. glcMode = gl::GL_RGB;
  143. break;
  144. case ColorMode::ARGB:
  145. if (image != nullptr)
  146. argb2rgba32(image, width, height);
  147. glcMode = gl::GL_RGBA;
  148. break;
  149. case ColorMode::BGRA:
  150. glcMode = gl::GL_BGRA;
  151. break;
  152. case ColorMode::RGBA:
  153. glcMode = gl::GL_RGBA;
  154. break;
  155. }
  156. gl::glPixelStorei(gl::GL_UNPACK_ALIGNMENT, 1);
  157. bindTexture(slot, s);
  158. gl::glTexImage2D(gl::GL_TEXTURE_2D, 0, gl::GLint(gl::GL_RGBA), width, height, 0, glcMode,
  159. gl::GL_UNSIGNED_BYTE, image);
  160. if (filter) {
  161. // Trilinear filtering
  162. gl::glTexParameteri(gl::GL_TEXTURE_2D, gl::GL_TEXTURE_WRAP_S, gl::GLint(gl::GL_REPEAT));
  163. gl::glTexParameteri(gl::GL_TEXTURE_2D, gl::GL_TEXTURE_WRAP_T, gl::GLint(gl::GL_REPEAT));
  164. gl::glTexParameteri(gl::GL_TEXTURE_2D, gl::GL_TEXTURE_MAG_FILTER, gl::GLint(gl::GL_LINEAR));
  165. gl::glTexParameteri(gl::GL_TEXTURE_2D, gl::GL_TEXTURE_MIN_FILTER,
  166. gl::GLint(gl::GL_LINEAR_MIPMAP_LINEAR));
  167. gl::glGenerateMipmap(gl::GL_TEXTURE_2D);
  168. } else {
  169. gl::glTexParameteri(gl::GL_TEXTURE_2D, gl::GL_TEXTURE_MIN_FILTER, gl::GLint(gl::GL_NEAREST));
  170. gl::glTexParameteri(gl::GL_TEXTURE_2D, gl::GL_TEXTURE_MAG_FILTER, gl::GLint(gl::GL_NEAREST));
  171. }
  172. return slot;
  173. }
  174. int TextureManager::numTextures(TextureStorage s) {
  175. return getIds(s).size();
  176. }
  177. void TextureManager::bindTextureId(unsigned int n, TextureStorage s, unsigned int unit) {
  178. orAssertLessThan(n, getIds(s).size());
  179. orAssertLessThan(unit, 80); //! \fixme Query GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS
  180. gl::glActiveTexture(gl::GL_TEXTURE0 + unit);
  181. gl::glBindTexture(gl::GL_TEXTURE_2D, getIds(s).at(n));
  182. }
  183. int TextureManager::bindTexture(unsigned int n, TextureStorage s) {
  184. orAssertLessThan(n, getIds(s).size());
  185. if ((n < getUnits(s).size()) && (getUnits(s).at(n) >= 0)) {
  186. bindTextureId(n, s, getUnits(s).at(n));
  187. return getUnits(s).at(n);
  188. } else {
  189. while (getUnits(s).size() <= n)
  190. getUnits(s).push_back(-1);
  191. getUnits(s).at(n) = nextFreeTextureUnit;
  192. bindTextureId(n, s, nextFreeTextureUnit);
  193. nextFreeTextureUnit++;
  194. return nextFreeTextureUnit - 1;
  195. }
  196. }
  197. unsigned int TextureManager::getTextureID(int n, TextureStorage s) {
  198. orAssertLessThan(n, getIds(s).size());
  199. return getIds(s).at(n);
  200. }
  201. void TextureManager::addTile(TextureTile* t) {
  202. tiles.push_back(t);
  203. }
  204. int TextureManager::numTiles() {
  205. return tiles.size();
  206. }
  207. TextureTile& TextureManager::getTile(int index) {
  208. orAssertGreaterThanEqual(index, 0);
  209. orAssertLessThan(index, tiles.size());
  210. return *tiles.at(index);
  211. }
  212. void TextureManager::addAnimatedTile(int index, int tile) {
  213. while (index >= animations.size())
  214. animations.push_back(std::vector<int>());
  215. animations.at(index).push_back(tile);
  216. }
  217. int TextureManager::numAnimatedTiles() {
  218. return animations.size();
  219. }
  220. int TextureManager::getFirstTileAnimation(int index) {
  221. orAssertLessThan(index, animations.size());
  222. orAssertGreaterThan(animations.at(index).size(), 0);
  223. return animations.at(index).at(0);
  224. }
  225. int TextureManager::getNextTileAnimation(int index, int tile) {
  226. orAssertLessThan(index, animations.size());
  227. for (int i = 0; i < animations.at(index).size(); i++) {
  228. if (animations.at(index).at(i) == tile) {
  229. if (i < (animations.at(index).size() - 1))
  230. return animations.at(index).at(i + 1);
  231. else
  232. return animations.at(index).at(0);
  233. }
  234. }
  235. return -1;
  236. }
  237. BufferManager* TextureManager::getBufferManager(int tex, TextureStorage store) {
  238. auto& v = (store == TextureStorage::GAME) ? gameBuffers : systemBuffers;
  239. /*! \fixme Strange +2 needed, or else OpenRaider crashes hard.
  240. * Normally, this should only be (tex). I had to increase it to
  241. * (tex + 1) some time ago when OpenRaider crashed in relation to
  242. * non-existing texture IDs. Now, after removing all custom Font
  243. * support (one texture less loaded at startup), OR crashed again.
  244. * Increasing this to (tex + 2) "fixed" the problem. Why?
  245. */
  246. while (v.size() <= (tex + 2)) {
  247. v.emplace_back(v.size(), store);
  248. }
  249. return &(v.at(tex));
  250. }
  251. void TextureManager::setPalette(int index, glm::vec4 color) {
  252. orAssertGreaterThanEqual(index, 0);
  253. orAssertLessThan(index, COLOR_PALETTE_SIZE);
  254. colorPalette[index] = color;
  255. }
  256. glm::vec4 TextureManager::getPalette(int index) {
  257. orAssertGreaterThanEqual(index, 0);
  258. orAssertLessThan(index, COLOR_PALETTE_SIZE);
  259. return colorPalette[index];
  260. }
  261. void TextureManager::addIndexedTexture(unsigned char* image, unsigned int width,
  262. unsigned int height) {
  263. unsigned char* img = new unsigned char[width * height];
  264. for (unsigned int i = 0; i < (width * height); i++)
  265. img[i] = image[i];
  266. indexedTextures.emplace_back(img, width, height);
  267. }
  268. void TextureManager::prepare() {
  269. for (int i = 0; i < indexedTextures.size(); i++) {
  270. auto tex = indexedTextures.at(i);
  271. unsigned char* img = std::get<0>(tex);
  272. unsigned int width = std::get<1>(tex);
  273. unsigned int height = std::get<2>(tex);
  274. unsigned char* image = new unsigned char[width * height * 4];
  275. for (unsigned int j = 0; j < (width * height); j++) {
  276. auto col = getPalette(img[j]);
  277. image[j * 4] = static_cast<unsigned char>(col.x * 255);
  278. image[(j * 4) + 1] = static_cast<unsigned char>(col.y * 255);
  279. image[(j * 4) + 2] = static_cast<unsigned char>(col.z * 255);
  280. image[(j * 4) + 3] = static_cast<unsigned char>(col.w * 255);
  281. }
  282. delete [] img;
  283. loadBufferSlot(image, width, height, ColorMode::RGBA, 32, TextureStorage::GAME, i, true);
  284. }
  285. }
  286. int TextureManager::loadImage(std::string filename, TextureStorage s, int slot) {
  287. if (stringEndsWith(filename, ".pcx")) {
  288. return loadPCX(filename, s, slot);
  289. } else {
  290. int x, y, n;
  291. unsigned char* data = stbi_load(filename.c_str(), &x, &y, &n, 0);
  292. if (data) {
  293. if ((n < 3) || (n > 4)) {
  294. Log::get(LOG_ERROR) << "Image \"" << filename << "\" has unsupported format ("
  295. << n << ")!" << Log::endl;
  296. stbi_image_free(data);
  297. return -2;
  298. }
  299. int id = loadBufferSlot(data, x, y, (n == 3) ? ColorMode::RGB : ColorMode::RGBA,
  300. (n == 3) ? 24 : 32, s, slot);
  301. stbi_image_free(data);
  302. return id;
  303. } else {
  304. Log::get(LOG_ERROR) << "Can't load image \"" << filename << "\"!" << Log::endl;
  305. return -1;
  306. }
  307. }
  308. }
  309. int TextureManager::loadPCX(std::string filename, TextureStorage s, int slot) {
  310. int error = pcxCheck(filename.c_str());
  311. if (!error) {
  312. unsigned char* image;
  313. unsigned int w, h, bpp;
  314. ColorMode c;
  315. error = pcxLoad(filename.c_str(), &image, &w, &h, &c, &bpp);
  316. if (!error) {
  317. unsigned char* image2 = scaleBuffer(image, &w, &h, bpp);
  318. if (image2) {
  319. delete [] image;
  320. image = image2;
  321. }
  322. int id = loadBufferSlot(image, w, h, c, bpp, s, slot);
  323. delete [] image;
  324. return id;
  325. }
  326. return -5;
  327. }
  328. return -4;
  329. }
  330. std::vector<unsigned int>& TextureManager::getIds(TextureStorage s) {
  331. if (s == TextureStorage::GAME)
  332. return mTextureIdsGame;
  333. else
  334. return mTextureIdsSystem;
  335. }
  336. std::vector<int>& TextureManager::getUnits(TextureStorage s) {
  337. if (s == TextureStorage::GAME)
  338. return gameUnits;
  339. else
  340. return systemUnits;
  341. }
  342. void TextureManager::display() {
  343. if (ImGui::CollapsingHeader("Texture Viewer")) {
  344. static bool game = Game::isLoaded();
  345. static int index = 0;
  346. ImGui::SliderInt("##texslide", &index, 0, TextureManager::numTextures(
  347. game ? TextureStorage::GAME : TextureStorage::SYSTEM) - 1);
  348. ImGui::SameLine();
  349. if (ImGui::Button("+##texplus", ImVec2(0, 0))) {
  350. if (index < (numTextures(
  351. game ? TextureStorage::GAME : TextureStorage::SYSTEM) - 1))
  352. index++;
  353. else
  354. index = 0;
  355. }
  356. ImGui::SameLine();
  357. if (ImGui::Button("-##texminus", ImVec2(0, 0))) {
  358. if (index > 0)
  359. index--;
  360. else
  361. index = numTextures(
  362. game ? TextureStorage::GAME : TextureStorage::SYSTEM) - 1;
  363. }
  364. if ((numTextures(TextureStorage::GAME) > 0)) {
  365. ImGui::SameLine();
  366. ImGui::Checkbox("Game##texgame", &game);
  367. } else {
  368. game = false;
  369. }
  370. if (index >= numTextures(game ? TextureStorage::GAME : TextureStorage::SYSTEM)) {
  371. index = numTextures(game ? TextureStorage::GAME : TextureStorage::SYSTEM) - 1;
  372. if (index < 0) {
  373. game = false;
  374. index = 0;
  375. }
  376. }
  377. auto bm = getBufferManager(index, game ? TextureStorage::GAME
  378. : TextureStorage::SYSTEM);
  379. ImGui::Image(bm, ImVec2(ImGui::GetColumnWidth() * 2 / 3, ImGui::GetColumnWidth() * 2 / 3));
  380. }
  381. if (ImGui::CollapsingHeader("Textile Viewer")) {
  382. if (numTiles() > 0) {
  383. static int index = 0;
  384. ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.5f);
  385. ImGui::SliderInt("##tileslide", &index, 0, numTiles() - 1);
  386. ImGui::PopItemWidth();
  387. ImGui::SameLine();
  388. if (ImGui::Button("+##tileplus", ImVec2(0, 0))) {
  389. if (index < (numTiles() - 1))
  390. index++;
  391. else
  392. index = 0;
  393. }
  394. ImGui::SameLine();
  395. if (ImGui::Button("-##tileminus", ImVec2(0, 0))) {
  396. if (index > 0)
  397. index--;
  398. else
  399. index = numTiles() - 1;
  400. }
  401. if (index >= numTiles())
  402. index = 0;
  403. auto& tile = getTile(index);
  404. auto bm = getBufferManager(tile.getTexture(), TextureStorage::GAME);
  405. ImVec2 size(ImGui::GetColumnWidth() * 2 / 3, ImGui::GetColumnWidth() * 2 / 3);
  406. auto uvA = tile.getUV(0);
  407. auto uvB = tile.getUV(2);
  408. ImVec2 uv1(uvA.x, uvA.y);
  409. ImVec2 uv2(uvB.x, uvB.y);
  410. ImGui::Image(bm, size, uv1, uv2);
  411. } else {
  412. ImGui::Text("No textiles are currently loaded...!");
  413. }
  414. }
  415. if (ImGui::CollapsingHeader("Animated Textile Viewer")) {
  416. if (numAnimatedTiles() > 0) {
  417. static int index = 0;
  418. static int tile = getFirstTileAnimation(index);
  419. if (ImGui::SliderInt("##animslide", &index, 0, numAnimatedTiles() - 1)) {
  420. tile = getFirstTileAnimation(index);
  421. }
  422. ImGui::SameLine();
  423. if (ImGui::Button("+##animplus", ImVec2(0, 0))) {
  424. if (index < (numAnimatedTiles() - 1))
  425. index++;
  426. else
  427. index = 0;
  428. tile = getFirstTileAnimation(index);
  429. }
  430. ImGui::SameLine();
  431. if (ImGui::Button("-##animminus", ImVec2(0, 0))) {
  432. if (index > 0)
  433. index--;
  434. else
  435. index = numAnimatedTiles() - 1;
  436. tile = getFirstTileAnimation(index);
  437. }
  438. if (index >= numAnimatedTiles()) {
  439. index = 0;
  440. tile = getFirstTileAnimation(index);
  441. }
  442. int next = getNextTileAnimation(index, tile);
  443. if (next == -1) {
  444. index = 0;
  445. tile = getFirstTileAnimation(index);
  446. }
  447. ImGui::SameLine();
  448. ImGui::Text("%d", tile);
  449. auto& t = getTile(tile);
  450. auto bm = getBufferManager(t.getTexture(), TextureStorage::GAME);
  451. ImVec2 size(ImGui::GetColumnWidth() * 2 / 3, ImGui::GetColumnWidth() * 2 / 3);
  452. auto uvA = t.getUV(0);
  453. auto uvB = t.getUV(2);
  454. ImVec2 uv1(uvA.x, uvA.y);
  455. ImVec2 uv2(uvB.x, uvB.y);
  456. ImGui::Image(bm, size, uv1, uv2);
  457. static int fr = 0;
  458. if (fr > 0) {
  459. fr--;
  460. } else {
  461. fr = RunTime::getFPS() / 5;
  462. tile = next;
  463. }
  464. } else {
  465. ImGui::Text("No animated textures are currently loaded...!");
  466. }
  467. }
  468. if (ImGui::CollapsingHeader("Sprite Sequence Viewer")) {
  469. if (World::sizeSprite() <= 0) {
  470. ImGui::Text("Please load a level containing sprites!");
  471. } else {
  472. static int index = 0;
  473. static int sprite = 0;
  474. if (ImGui::SliderInt("##spriteslide", &index, 0, World::sizeSpriteSequence() - 1)) {
  475. sprite = 0;
  476. }
  477. ImGui::SameLine();
  478. if (ImGui::Button("+##spriteplus", ImVec2(0, 0))) {
  479. if (index < (World::sizeSpriteSequence() - 1))
  480. index++;
  481. else
  482. index = 0;
  483. sprite = 0;
  484. }
  485. ImGui::SameLine();
  486. if (ImGui::Button("-##spriteminus", ImVec2(0, 0))) {
  487. if (index > 0)
  488. index--;
  489. else
  490. index = World::sizeSpriteSequence() - 1;
  491. sprite = 0;
  492. }
  493. if (index >= World::sizeSpriteSequence()) {
  494. index = 0;
  495. sprite = 0;
  496. }
  497. if (sprite >= World::getSpriteSequence(index).size()) {
  498. sprite = 0;
  499. }
  500. ImGui::SameLine();
  501. ImGui::Text("Sprite %d/%d", sprite + 1, World::getSpriteSequence(index).size());
  502. auto& s = World::getSprite(World::getSpriteSequence(index).getStart() + sprite);
  503. auto bm = getBufferManager(s.getTexture(), TextureStorage::GAME);
  504. ImVec2 size(ImGui::GetColumnWidth() * 2 / 3, ImGui::GetColumnWidth() * 2 / 3);
  505. auto uv = s.getUVs();
  506. ImVec2 uv1(uv.x, uv.w);
  507. ImVec2 uv2(uv.z, uv.y);
  508. ImGui::Image(bm, size, uv1, uv2);
  509. static int fr = 0;
  510. if (fr > 0) {
  511. fr--;
  512. } else {
  513. fr = RunTime::getFPS() / 10;
  514. if (sprite < (World::getSpriteSequence(index).size() - 1))
  515. sprite++;
  516. else
  517. sprite = 0;
  518. }
  519. }
  520. }
  521. }