diff --git a/src/core.h b/src/core.h index 3dbcd85..fcdcea1 100644 --- a/src/core.h +++ b/src/core.h @@ -587,7 +587,7 @@ namespace Core { #include "texture.h" #include "shader.h" -//#include "video.h" +#include "video.h" namespace Core { diff --git a/src/game.h b/src/game.h index 1ebab4a..fa25c60 100644 --- a/src/game.h +++ b/src/game.h @@ -17,6 +17,7 @@ namespace Game { Input::stopJoyVibration(); delete level; level = new Level(*lvl); + level->init(); UI::game = level; #if !defined(_OS_PSP) && !defined(_OS_CLOVER) UI::helpTipTime = 5.0f; diff --git a/src/gameflow.h b/src/gameflow.h index ead1775..363b329 100644 --- a/src/gameflow.h +++ b/src/gameflow.h @@ -193,7 +193,7 @@ namespace TR { struct LevelInfo { const char *name; const char *title; - int ambientTrack; + int track; } LEVEL_INFO[LVL_MAX] = { // TR1 { "" , "Custom Level", TRACK_TR1_CAVES }, @@ -243,8 +243,8 @@ namespace TR { { "CATACOMB" , "Catacombs of the Talion", TRACK_TR2_TIBET_2 }, { "ICECAVE" , "Ice Palace", TRACK_TR2_TIBET_2 }, { "EMPRTOMB" , "Temple of Xian", TRACK_TR2_CHINA_2 }, - { "FLOATING" , "Floating Islands", TRACK_TR2_CHINA_2 }, { "CUT4" , "", TRACK_TR2_CUT_4 }, + { "FLOATING" , "Floating Islands", TRACK_TR2_CHINA_2 }, { "XIAN" , "The Dragon's Lair", TRACK_TR2_CHINA_2 }, { "HOUSE" , "Home Sweet Home", NO_TRACK }, // TR3 @@ -341,6 +341,7 @@ namespace TR { case 636660 : version = VER_TR1_PSX; case 512104 : return LVL_TR1_CUT_3; case 1686748 : version = VER_TR1_PSX; + case 3094342 : case 3094020 : return LVL_TR1_10B; case 940398 : version = VER_TR1_PSX; case 879582 : return LVL_TR1_CUT_4; @@ -506,7 +507,7 @@ namespace TR { LevelID getEndId(Version version) { switch (version & VER_VERSION) { - case VER_TR1 : return LVL_TR1_10C; + case VER_TR1 : return LVL_TR1_END2; case VER_TR2 : return LVL_TR2_HOUSE; case VER_TR3 : return LVL_TR3_CHAMBER; } @@ -739,7 +740,7 @@ namespace TR { new Stream(title, callback, userData); } - const char* getGameScreen(Version version, LevelID id) { + const char* getGameScreen(LevelID id) { switch (id) { // TR1 case LVL_TR1_TITLE : @@ -855,10 +856,65 @@ namespace TR { CHECK_FILE("pix/ANTARC.BMP"); return "level/3/ANTARC.PNG"; - default : ; + default : return NULL; } + } - return NULL; + const char* getGameVideo(LevelID id) { + switch (id) { + // TR1 + case LVL_TR1_TITLE : + CHECK_FILE("FMV/CAFE.RPL"); + return "video/1/CAFE.RPL"; + case LVL_TR1_GYM : + CHECK_FILE("FMV/MANSION.RPL"); + return "video/1/MANSION.RPL"; + case LVL_TR1_1 : + CHECK_FILE("FMV/SNOW.RPL"); + return "video/1/SNOW.RPL"; + case LVL_TR1_4 : + CHECK_FILE("FMV/LIFT.RPL"); + return "video/1/LIFT.RPL"; + case LVL_TR1_8A : + CHECK_FILE("FMV/VISION.RPL"); + return "video/1/VISION.RPL"; + case LVL_TR1_10A : + CHECK_FILE("FMV/CANYON.RPL"); + return "video/1/CANYON.RPL"; + case LVL_TR1_10B : + CHECK_FILE("FMV/PIRAMID.RPL"); + return "video/1/PIRAMID.RPL"; + case LVL_TR1_CUT_4 : + CHECK_FILE("FMV/PRISON.RPL"); + return "video/1/PRISON.RPL"; + case LVL_TR1_EGYPT : + CHECK_FILE("FMV/END.RPL"); + return "video/1/END.RPL"; + // TR2 + case LVL_TR2_TITLE : + CHECK_FILE("fmv/ANCIENT.RPL"); + return "video/2/ANCIENT.RPL"; + case LVL_TR2_WALL : + CHECK_FILE("fmv/MODERN.RPL"); + return "video/2/MODERN.RPL"; + case LVL_TR2_RIG : + CHECK_FILE("fmv/LANDING.RPL"); + return "video/2/LANDING.RPL"; + case LVL_TR2_UNWATER : + CHECK_FILE("fmv/MS.RPL"); + return "video/2/MS.RPL"; + case LVL_TR2_SKIDOO : + CHECK_FILE("fmv/CRASH.RPL"); + return "video/2/CRASH.RPL"; + case LVL_TR2_EMPRTOMB : + CHECK_FILE("fmv/JEEP.RPL"); + return "video/2/JEEP.RPL"; + case LVL_TR2_HOUSE : + CHECK_FILE("fmv/END.RPL"); + return "video/2/END.RPL"; + // TR3 + default : return NULL; + } } #define FOG_DIST (1.0f / (18 * 1024)) diff --git a/src/gapi_gl.h b/src/gapi_gl.h index 9a0615c..997c430 100644 --- a/src/gapi_gl.h +++ b/src/gapi_gl.h @@ -592,10 +592,18 @@ namespace GAPI { } #endif + + void *pix = data; + if (data && !Core::support.texNPOT && (width != origWidth || height != origHeight)) + pix = NULL; + for (int i = 0; i < 6; i++) { - glTexImage2D(cube ? (GL_TEXTURE_CUBE_MAP_POSITIVE_X + i) : GL_TEXTURE_2D, 0, desc.ifmt, width, height, 0, desc.fmt, desc.type, data); + glTexImage2D(cube ? (GL_TEXTURE_CUBE_MAP_POSITIVE_X + i) : GL_TEXTURE_2D, 0, desc.ifmt, width, height, 0, desc.fmt, desc.type, pix); if (!cube) break; } + + if (pix != data) + update(data); } void deinit() { diff --git a/src/inventory.h b/src/inventory.h index e1ff1fe..8c35fbc 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -210,6 +210,7 @@ struct Inventory { IGame *game; Texture *background[2]; + Video *video; bool active; bool chosen; @@ -454,6 +455,10 @@ struct Inventory { static void loadTitleBG(Stream *stream, void *userData) { Inventory *inv = (Inventory*)userData; + + if (!inv->video) + inv->skipVideo(); // play background track etc. + if (!stream) { inv->titleTimer = 0.0f; return; @@ -464,6 +469,13 @@ struct Inventory { delete stream; } + static void loadVideo(Stream *stream, void *userData) { + Inventory *inv = (Inventory*)userData; + if (stream) + inv->video = new Video(stream); + new Stream(TR::getGameScreen(inv->game->getLevel()->id), loadTitleBG, inv); + } + Inventory(IGame *game) : game(game), active(false), chosen(false), index(0), targetIndex(0), page(PAGE_OPTION), targetPage(PAGE_OPTION), itemsCount(0), playerIndex(0), changeTimer(0.0f), nextLevel(TR::LVL_MAX), lastKey(cMAX) { TR::LevelID id = game->getLevel()->id; @@ -505,13 +517,6 @@ struct Inventory { memset(background, 0, sizeof(background)); - const char *titleBG = TR::getGameScreen(level->version, level->id); - if (titleBG) { - titleTimer = TITLE_LOADING; - new Stream(titleBG, loadTitleBG, this); - } else - titleTimer = 0.0f; - if (level->isTitle()) { add(TR::Entity::INV_HOME); } else { @@ -523,9 +528,14 @@ struct Inventory { memset(pageItemIndex, 0, sizeof(pageItemIndex)); waitForKey = NULL; + video = NULL; + + titleTimer = TITLE_LOADING; } ~Inventory() { + delete video; + for (int i = 0; i < itemsCount; i++) delete items[i]; @@ -533,6 +543,10 @@ struct Inventory { delete background[i]; } + void init() { + new Stream(TR::getGameVideo(game->getLevel()->id), loadVideo, this); + } + bool isActive() { return active || phaseRing > 0.0f; } @@ -651,7 +665,10 @@ struct Inventory { return false; } - bool toggle(int playerIndex = 0, Page curPage = PAGE_INVENTORY, TR::Entity::Type type = TR::Entity::LARA) { + void toggle(int playerIndex = 0, Page curPage = PAGE_INVENTORY, TR::Entity::Type type = TR::Entity::LARA) { + if (titleTimer != 0.0f || (isActive() != active)) + return; + Input::stopJoyVibration(); this->playerIndex = playerIndex; @@ -686,7 +703,6 @@ struct Inventory { // chooseItem(); } } - return active; } void doPhase(bool increase, float speed, float &value) { @@ -898,7 +914,30 @@ struct Inventory { } }; + void skipVideo() { + delete video; + video = NULL; + game->playTrack(0); + if (game->getLevel()->isTitle()) { + titleTimer = 0.0f; + toggle(0, Inventory::PAGE_OPTION); + } + Input::reset(); + } + void update() { + if (video && (Input::state[0][cInventory] || Input::state[1][cInventory])) + skipVideo(); + + if (video) { + video->update(); + if (video->isPlaying) + return; + skipVideo(); + } + + if (video || titleTimer == TITLE_LOADING) return; + if (titleTimer != TITLE_LOADING && titleTimer > 0.0f) { titleTimer -= Core::deltaTime; if (titleTimer < 0.0f) @@ -1139,14 +1178,14 @@ struct Inventory { // vertical blur Core::setTarget(background[1], RT_STORE_COLOR); game->setShader(Core::passFilter, Shader::FILTER_BLUR, false, false); - Core::active.shader->setParam(uParam, vec4(0, 1, 1.0f / INVENTORY_BG_SIZE, 0));; + Core::active.shader->setParam(uParam, vec4(0, 1.0f / INVENTORY_BG_SIZE, 0, 0)); background[0]->bind(sDiffuse); game->getMesh()->renderQuad(); // horizontal blur Core::setTarget(background[0], RT_STORE_COLOR); game->setShader(Core::passFilter, Shader::FILTER_BLUR, false, false); - Core::active.shader->setParam(uParam, vec4(1, 0, 1.0f / INVENTORY_BG_SIZE, 0));; + Core::active.shader->setParam(uParam, vec4(1.0f / INVENTORY_BG_SIZE, 0, 0, 0)); background[1]->bind(sDiffuse); game->getMesh()->renderQuad(); @@ -1307,7 +1346,7 @@ struct Inventory { } } - void renderTitleBG(float sx = 1.0f, float sy = 1.0f) { + void renderTitleBG(float sx = 1.0f, float sy = 1.0f, uint8 alpha = 255) { float aspectSrc, aspectDst, aspectImg, ax, ay; if (background[0]) { @@ -1331,14 +1370,7 @@ struct Inventory { Core::mModel.scale(vec3(1.0f / 32767.0f)); #endif - uint8 alpha; - if (!isActive() && titleTimer > 0.0f && titleTimer < 1.0f) { - Core::setBlendMode(bmAlpha); - alpha = uint8(titleTimer * 255); - } else { - Core::setBlendMode(bmNone); - alpha = 255; - } + Core::setBlendMode(alpha < 255 ? bmAlpha : bmNone); Index indices[6 * 3] = { 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11 }; Vertex vertices[4 * 3]; @@ -1414,7 +1446,7 @@ struct Inventory { background[0]->bind(sDiffuse); game->setShader(Core::passFilter, Shader::FILTER_UPSCALE, false, false); - Core::active.shader->setParam(uParam, vec4(float(Core::active.textures[sDiffuse]->width), float(Core::active.textures[sDiffuse]->height), 0.0f, 0.0f)); + Core::active.shader->setParam(uParam, vec4(float(Core::active.textures[sDiffuse]->width), float(Core::active.textures[sDiffuse]->height), Core::getTime() * 0.001f, 0.0f)); game->getMesh()->renderBuffer(indices, COUNT(indices), vertices, COUNT(vertices)); } @@ -1452,16 +1484,22 @@ struct Inventory { Core::setDepthTest(false); + uint8 alpha; + if (!isActive() && titleTimer > 0.0f && titleTimer < 1.0f) + alpha = uint8(titleTimer * 255); + else + alpha = 255; + if (Core::settings.detail.stereo == Core::Settings::STEREO_VR) { if (game->getLevel()->isTitle()) - renderTitleBG(); + renderTitleBG(1.0f, 1.0f, alpha); else renderGameBG(); } else { if (background[1]) renderGameBG(); else - renderTitleBG(); + renderTitleBG(1.0f, 1.0f, alpha); } Core::setBlendMode(bmAlpha); @@ -1504,6 +1542,24 @@ struct Inventory { } void render(float aspect) { + if (video) { + Core::setDepthTest(false); + video->render(); + + Texture *tmp = background[0]; + + background[0] = video->frameTex[0]; + renderTitleBG(1.0f, 1.2f, 255); + + background[0] = video->frameTex[1]; + renderTitleBG(1.0f, 1.2f, clamp(int((video->time / video->step) * 255), 0, 255)); + + background[0] = tmp; + + Core::setDepthTest(true); + return; + } + if (!isActive() && titleTimer == 0.0f) return; diff --git a/src/level.h b/src/level.h index 20851f6..34047d4 100644 --- a/src/level.h +++ b/src/level.h @@ -26,7 +26,7 @@ extern void loadAsync(Stream *stream, void *userData); struct Level : IGame { TR::Level level; - Inventory inventory; + Inventory *inventory; Texture *atlas; MeshBuilder *mesh; @@ -57,8 +57,6 @@ struct Level : IGame { float cutsceneWaitTimer; float animTexTimer; - Texture *cube360; - // IGame implementation ======== virtual void loadLevel(TR::LevelID id) { if (isEnded) return; @@ -86,7 +84,7 @@ struct Level : IGame { virtual void saveGame(int slot) { LOG("Save Game... "); - char *data = new char[sizeof(TR::SaveGame) + sizeof(TR::SaveGame::Item) * inventory.itemsCount + sizeof(TR::SaveGame::CurrentState) + sizeof(TR::SaveGame::Entity) * level.entitiesCount]; // oversized + char *data = new char[sizeof(TR::SaveGame) + sizeof(TR::SaveGame::Item) * inventory->itemsCount + sizeof(TR::SaveGame::CurrentState) + sizeof(TR::SaveGame::Entity) * level.entitiesCount]; // oversized char *ptr = data; TR::SaveGame *save = (TR::SaveGame*)ptr; @@ -107,9 +105,9 @@ struct Level : IGame { // inventory items currentState->progress.itemsCount = 0; - for (int i = 0; i < inventory.itemsCount; i++) { + for (int i = 0; i < inventory->itemsCount; i++) { TR::SaveGame::Item *item = (TR::SaveGame::Item*)ptr; - Inventory::Item *invItem = inventory.items[i]; + Inventory::Item *invItem = inventory->items[i]; if (!TR::Entity::isPickup(TR::Entity::convFromInv(invItem->type))) continue; @@ -167,7 +165,7 @@ struct Level : IGame { for (int i = 0; i < currentState->progress.itemsCount; i++) { TR::SaveGame::Item *item = (TR::SaveGame::Item*)ptr; - inventory.add(TR::Entity::Type(item->type), item->count, false); + inventory->add(TR::Entity::Type(item->type), item->count, false); ptr += sizeof(*item); } @@ -203,11 +201,11 @@ struct Level : IGame { } void clearInventory() { - int i = inventory.itemsCount; + int i = inventory->itemsCount; while (i--) { - if (TR::Entity::isPickup(TR::Entity::convFromInv(inventory.items[i]->type))) - inventory.remove(i); + if (TR::Entity::isPickup(TR::Entity::convFromInv(inventory->items[i]->type))) + inventory->remove(i); } } @@ -281,10 +279,10 @@ struct Level : IGame { waterCache = Core::settings.detail.water > Core::Settings::LOW ? new WaterCache(this) : NULL; } - if (redraw && inventory.active && !level.isTitle()) { + if (redraw && inventory->active && !level.isTitle()) { Core::reset(); Core::beginFrame(); - inventory.prepareBackground(); + inventory->prepareBackground(); Core::endFrame(); } } @@ -559,20 +557,20 @@ struct Level : IGame { virtual bool invUse(int playerIndex, TR::Entity::Type type) { if (!players[playerIndex]->useItem(type)) - return inventory.use(type); + return inventory->use(type); return true; } virtual void invAdd(TR::Entity::Type type, int count) { - inventory.add(type, count); + inventory->add(type, count); } virtual int* invCount(TR::Entity::Type type) { - return inventory.getCountPtr(type); + return inventory->getCountPtr(type); } virtual bool invChooseKey(int playerIndex, TR::Entity::Type hole) { - return inventory.chooseKey(playerIndex, hole); + return inventory->chooseKey(playerIndex, hole); } virtual Sound::Sample* playSound(int id, const vec3 &pos = vec3(0.0f), int flags = 0) const { @@ -609,7 +607,7 @@ struct Level : IGame { void stopChannel(Sound::Sample *channel) { if (channel == sndTrack) { sndTrack = NULL; - if (level.state.flags.track == TR::LEVEL_INFO[level.id].ambientTrack) // play ambient track + if (level.state.flags.track == TR::LEVEL_INFO[level.id].track) // play ambient track playTrack(0); } } @@ -640,7 +638,7 @@ struct Level : IGame { virtual void playTrack(uint8 track) { if (track == 0) - track = TR::LEVEL_INFO[level.id].ambientTrack; + track = TR::LEVEL_INFO[level.id].track; if (level.state.flags.track == track) { // if (sndTrack) { @@ -660,7 +658,7 @@ struct Level : IGame { if (track == 0xFF) return; int flags = Sound::MUSIC; - if (track == TR::LEVEL_INFO[level.id].ambientTrack) + if (track == TR::LEVEL_INFO[level.id].track) flags |= Sound::LOOP; waitTrack = true; @@ -672,7 +670,7 @@ struct Level : IGame { } //============================== - Level(Stream &stream) : level(stream), inventory(this), waitTrack(false), isEnded(false), cutsceneWaitTimer(0.0f), animTexTimer(0.0f) { + Level(Stream &stream) : level(stream), waitTrack(false), isEnded(false), cutsceneWaitTimer(0.0f), animTexTimer(0.0f) { #ifdef _OS_PSP GAPI::freeEDRAM(); #endif @@ -688,6 +686,8 @@ struct Level : IGame { mesh = new MeshBuilder(level, atlas); initOverrides(); + inventory = new Inventory(this); + for (int i = 0; i < level.entitiesBaseCount; i++) { TR::Entity &e = level.entities[i]; e.controller = initController(i); @@ -724,18 +724,14 @@ struct Level : IGame { playSound(src.id, vec3(float(src.x), float(src.y), float(src.z)), flags); } - } else { - inventory.toggle(0, Inventory::PAGE_OPTION); } setClipParams(1.0f, NO_CLIP_PLANE); effect = TR::Effect::NONE; - cube360 = NULL; sndWater = sndTrack = NULL; - playTrack(0); /* if (level.id == TR::LVL_TR2_RIG) { lara->animation.setAnim(level.models[level.extra.laraSpec].animation); @@ -747,7 +743,7 @@ struct Level : IGame { } virtual ~Level() { - delete cube360; + delete inventory; for (int i = 0; i < level.entitiesCount; i++) delete (Controller*)level.entities[i].controller; @@ -763,6 +759,10 @@ struct Level : IGame { Sound::stopAll(); } + void init() { + inventory->init(); + } + void addPlayer(int index) { if (level.isCutsceneLevel()) return; @@ -1477,7 +1477,7 @@ struct Level : IGame { if (isModel) { // model vec3 pos = controller->getPos(); if (ambientCache) { - if (!controller->getEntity().isDoor()) { // no advanced ambient lighting for secret (all) doors + if (!controller->getEntity().isDoor() && !controller->getEntity().isBlock()) { // no advanced ambient lighting for secret (all) doors and blocks AmbientCache::Cube cube; ambientCache->getAmbient(roomIndex, pos, cube); if (cube.status == AmbientCache::Cube::READY) @@ -1501,8 +1501,13 @@ struct Level : IGame { } void update() { + if (inventory->video) { + inventory->update(); + return; + } + if (level.isCutsceneLevel() && waitTrack) { - if (!sndTrack && TR::LEVEL_INFO[level.id].ambientTrack != TR::NO_TRACK) { + if (!sndTrack && TR::LEVEL_INFO[level.id].track != TR::NO_TRACK) { if (camera->timer > 0.0f) // for the case that audio stops before animation ends loadNextLevel(); return; @@ -1521,7 +1526,7 @@ struct Level : IGame { } } - if ((Input::state[0][cInventory] || Input::state[1][cInventory]) && !level.isTitle() && inventory.titleTimer < 1.0f && !inventory.active && inventory.lastKey == cMAX) { + if ((Input::state[0][cInventory] || Input::state[1][cInventory]) && !level.isTitle() && inventory->titleTimer < 1.0f && !inventory->active && inventory->lastKey == cMAX) { int playerIndex = Input::state[0][cInventory] ? 0 : 1; if (level.isCutsceneLevel()) { @@ -1530,21 +1535,21 @@ struct Level : IGame { } if (player->health <= 0.0f) - inventory.toggle(playerIndex, Inventory::PAGE_OPTION, TR::Entity::INV_PASSPORT); + inventory->toggle(playerIndex, Inventory::PAGE_OPTION, TR::Entity::INV_PASSPORT); else - inventory.toggle(playerIndex); + inventory->toggle(playerIndex); } - inventory.update(); + inventory->update(); - if (inventory.titleTimer > 1.0f) + if (inventory->titleTimer > 1.0f) return; UI::update(); float volWater, volTrack; - if (inventory.isActive() || level.isTitle()) { + if (inventory->isActive() || level.isTitle()) { Sound::reverb.setRoomSize(vec3(1.0f)); volWater = 0.0f; volTrack = level.isTitle() ? 0.9f : 0.0f; @@ -2189,7 +2194,7 @@ struct Level : IGame { #ifdef DEBUG_RENDER void renderDebug() { - if (level.isTitle() || inventory.titleTimer > 1.0f) return; + if (level.isTitle() || inventory->titleTimer > 1.0f) return; Core::setViewport(Core::x, Core::y, Core::width, Core::height); camera->setup(true); @@ -2432,6 +2437,11 @@ struct Level : IGame { } void renderPrepare() { + if (inventory->video) { + inventory->render(1.0); + return; + } + if (ambientCache) ambientCache->processQueue(); @@ -2534,7 +2544,7 @@ struct Level : IGame { } void renderUI() { - if (level.isCutsceneLevel() || inventory.titleTimer > 1.0f) return; + if (level.isCutsceneLevel() || inventory->titleTimer > 1.0f) return; UI::begin(); UI::updateAspect(camera->aspect); @@ -2551,7 +2561,7 @@ struct Level : IGame { if (oxygen <= 0.2f) oxygen = 0.0f; } - float eye = inventory.active ? 0.0f : UI::width * Core::eye * 0.02f; + float eye = inventory->active ? 0.0f : UI::width * Core::eye * 0.02f; vec2 pos; if (Core::settings.detail.stereo == Core::Settings::STEREO_VR) @@ -2564,14 +2574,14 @@ struct Level : IGame { pos.y += 16.0f; } - if ((!inventory.active && (!player->emptyHands() || player->damageTime > 0.0f || health <= 0.2f))) { + if ((!inventory->active && (!player->emptyHands() || player->damageTime > 0.0f || health <= 0.2f))) { UI::renderBar(UI::BAR_HEALTH, pos, size, health); pos.y += 32.0f; - if (!inventory.active && !player->emptyHands()) { // ammo - int index = inventory.contains(player->getCurrentWeaponInv()); + if (!inventory->active && !player->emptyHands()) { // ammo + int index = inventory->contains(player->getCurrentWeaponInv()); if (index > -1) - inventory.renderItemCount(inventory.items[index], pos, size.x); + inventory->renderItemCount(inventory->items[index], pos, size.x); } } } @@ -2597,21 +2607,21 @@ struct Level : IGame { Core::eye = float(eye); - if (level.isTitle() || inventory.titleTimer > 0.0f) - inventory.renderBackground(); - inventory.render(aspect); + if (level.isTitle() || inventory->titleTimer > 0.0f) + inventory->renderBackground(); + inventory->render(aspect); UI::begin(); UI::updateAspect(aspect); - inventory.renderUI(); + inventory->renderUI(); UI::end(); } void renderInventory() { Core::setTarget(NULL, RT_CLEAR_DEPTH | RT_STORE_COLOR); - if (!(level.isTitle() || inventory.titleTimer > 0.0f)) - inventory.renderBackground(); + if (!(level.isTitle() || inventory->titleTimer > 0.0f)) + inventory->renderBackground(); float oldEye = Core::eye; @@ -2626,7 +2636,10 @@ struct Level : IGame { } void render() { - bool title = inventory.isActive() || level.isTitle(); + if (inventory->video) + return; + + bool title = inventory->isActive() || level.isTitle(); bool copyBg = title && lastTitle != title; lastTitle = title; @@ -2640,11 +2653,11 @@ struct Level : IGame { } if (copyBg) { - inventory.prepareBackground(); + inventory->prepareBackground(); } if (!level.isTitle()) { - if (inventory.phaseRing < 1.0f && inventory.titleTimer <= 1.0f) { + if (inventory->phaseRing < 1.0f && inventory->titleTimer <= 1.0f) { renderGame(true); title = false; } diff --git a/src/platform/win/OpenLara.vcxproj b/src/platform/win/OpenLara.vcxproj index 9350774..e0f34da 100644 --- a/src/platform/win/OpenLara.vcxproj +++ b/src/platform/win/OpenLara.vcxproj @@ -225,6 +225,7 @@ + diff --git a/src/platform/win/OpenLara.vcxproj.filters b/src/platform/win/OpenLara.vcxproj.filters index def799d..ec75d5b 100644 --- a/src/platform/win/OpenLara.vcxproj.filters +++ b/src/platform/win/OpenLara.vcxproj.filters @@ -52,6 +52,7 @@ libs\tinf + diff --git a/src/shader.h b/src/shader.h index 4274a60..1f971c5 100644 --- a/src/shader.h +++ b/src/shader.h @@ -10,7 +10,7 @@ struct Shader : GAPI::Shader { /* shader */ SPRITE = 0, FLASH = 1, ROOM = 2, ENTITY = 3, MIRROR = 4, /* filter */ FILTER_UPSCALE = 0, FILTER_DOWNSAMPLE = 1, FILTER_GRAYSCALE = 2, FILTER_BLUR = 3, FILTER_EQUIRECTANGULAR = 4, /* water */ WATER_DROP = 0, WATER_STEP = 1, WATER_CAUSTICS = 2, WATER_MASK = 3, WATER_COMPOSE = 4, - MAX = 6 + MAX = 5 }; Shader(Core::Pass pass, Type type, int *def, int defCount) : GAPI::Shader() { diff --git a/src/shaders/filter.glsl b/src/shaders/filter.glsl index 0376918..c0a3ab5 100644 --- a/src/shaders/filter.glsl +++ b/src/shaders/filter.glsl @@ -46,7 +46,7 @@ uniform vec4 uParam; const vec3 offset = vec3(0.0, 1.3846153846, 3.2307692308); const vec3 weight = vec3(0.2270270270, 0.3162162162, 0.0702702703); - vec2 dir = uParam.xy * uParam.z; + vec2 dir = uParam.xy; vec4 color = texture2D(sDiffuse, vTexCoord) * weight[0]; color += texture2D(sDiffuse, vTexCoord + dir * offset[1]) * weight[1]; color += texture2D(sDiffuse, vTexCoord - dir * offset[1]) * weight[1]; @@ -67,7 +67,6 @@ uniform vec4 uParam; } #endif - vec4 upscale() { // https://www.shadertoy.com/view/XsfGDn vec2 uv = vTexCoord * uParam.xy + 0.5; vec2 iuv = floor(uv); diff --git a/src/sound.h b/src/sound.h index 29d5bbf..533b1eb 100644 --- a/src/sound.h +++ b/src/sound.h @@ -481,6 +481,10 @@ namespace Sound { bool isPlaying; bool stopAfterFade; + Sample(Decoder *decoder, float volume, float pitch, int flags, int id) : uniquePtr(NULL), decoder(decoder), volume(volume), volumeTarget(volume), volumeDelta(0.0f), pitch(pitch), flags(flags), id(id) { + isPlaying = decoder != NULL; + } + Sample(Stream *stream, const vec3 *pos, float volume, float pitch, int flags, int id) : uniquePtr(pos), decoder(NULL), volume(volume), volumeTarget(volume), volumeDelta(0.0f), pitch(pitch), flags(flags), id(id) { this->pos = pos ? *pos : vec3(0.0f); @@ -811,6 +815,12 @@ namespace Sound { return NULL; } + Sample* play(Decoder *decoder) { + if (channelsCount < SND_CHANNELS_MAX) + return channels[channelsCount++] = new Sample(decoder, 1.0f, 1.0f, MUSIC, -1); + return NULL; + } + void stop(int id = -1) { OS_LOCK(lock); diff --git a/src/utils.h b/src/utils.h index 3e7b02f..1b97f5e 100644 --- a/src/utils.h +++ b/src/utils.h @@ -180,6 +180,12 @@ int nextPow2(uint32 x) { return x; } +inline uint32 log2i(uint32 value) { + int res = 0; + for(; value; value >>= 1, res++); + return res ? res - 1 : res; +} + uint32 fnv32(const char *data, int32 size, uint32 hash = 0x811c9dc5) { for (int i = 0; i < size; i++) hash = (hash ^ data[i]) * 0x01000193; @@ -1200,7 +1206,17 @@ struct Stream { } } - Stream(const char *name, Callback *callback = NULL, void *userData = NULL) : callback(callback), userData(userData), data(NULL), name(NULL), size(-1), pos(0), endian(eLittle) { + Stream(const char *name, Callback *callback = NULL, void *userData = NULL) : callback(callback), userData(userData), f(NULL), data(NULL), name(NULL), size(-1), pos(0), endian(eLittle) { + if (!name && callback) { + callback(NULL, userData); + delete this; + return; + } + + if (!name) { + ASSERT(false); + } + if (contentDir[0] && (!cacheDir[0] || !strstr(name, cacheDir))) { char path[255]; path[0] = 0; @@ -1277,8 +1293,10 @@ struct Stream { } void setPos(int pos) { - this->pos = pos; - if (f) fseek(f, pos, SEEK_SET); + if (this->pos != pos) { + this->pos = pos; + if (f) fseek(f, pos, SEEK_SET); + } } void seek(int offset) { @@ -1287,13 +1305,14 @@ struct Stream { pos += offset; } - void raw(void *data, int count) { - if (!count) return; + int raw(void *data, int count) { + if (!count) return 0; if (f) - fread(data, 1, count, f); + count = fread(data, 1, count, f); else memcpy(data, this->data + pos, count); pos += count; + return count; } template @@ -1427,6 +1446,29 @@ struct BitStream { BitStream(uint8 *data, int size) : data(data), end(data + size), index(0), value(0) {} + uint32 read(int count) { + uint32 bits = 0; + + int m = count - 1; + + while (count--) { + if (!index) { + ASSERT(data < end); + value = *data++; + index = 8; + } + + if (value & 1) + bits |= (1 << (m - count)); + + value >>= 1; + + index--; + } + + return bits; + } + uint8 readBits(int count) { uint32 bits = 0; diff --git a/src/video.h b/src/video.h new file mode 100644 index 0000000..8f5ba73 --- /dev/null +++ b/src/video.h @@ -0,0 +1,437 @@ +#ifndef H_VIDEO +#define H_VIDEO + +#include "utils.h" +#include "texture.h" +#include "sound.h" + +struct Video { + + struct Decoder : Sound::Decoder { + int width, height, fps; + + Decoder(Stream *stream) : Sound::Decoder(stream, 2) {} + virtual ~Decoder() { /* delete stream; */ } + virtual bool decode(uint8 *frame) { return false; } + }; + + // based on ffmpeg code https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/escape124.c + struct Escape124 : Decoder { + int vfmt, bpp; + int sfmt, rate, channels, bps; + int framesCount, chunksCount, offset; + int curVideoPos, curVideoChunk; + int curAudioPos, curAudioChunk; + + uint32 *prevFrame, *nextFrame; + Sound::Frame prevSample; + + struct Chunk { + int offset; + int videoSize; + int audioSize; + } *chunks; + + union MacroBlock { + uint32 pixels[4]; + }; + + union SuperBlock { + uint32 pixels[64]; + }; + + struct Codebook { + uint32 size; + uint32 depth; + MacroBlock *blocks; + } codebook[3]; + + Escape124(Stream *stream) : Decoder(stream), chunks(NULL) { + for (int i = 0; i < 4; i++) + skipLine(); + + vfmt = readValue(); // video format + width = readValue(); // x size in pixels + height = readValue(); // y size in pixels + bpp = readValue(); // bits per pixel RGB + fps = readValue(); // frames per second + sfmt = readValue(); // sound format + rate = readValue(); // Hz Samples + channels = readValue(); // channel + bps = readValue(); // bits per sample (LINEAR UNSIGNED) + framesCount = readValue(); // frames per chunk + chunksCount = readValue() + 1; // number of chunks + skipLine(); // even chunk size + skipLine(); // odd chunk size + offset = readValue(); // offset to chunk cat + skipLine(); // offset to sprite + skipLine(); // size of sprite + skipLine(); // offset to key frames + + stream->setPos(offset); + + chunks = new Chunk[chunksCount]; + for (int i = 0; i < chunksCount; i++) { + chunks[i].offset = readValue(); + chunks[i].videoSize = readValue(); + chunks[i].audioSize = readValue(); + } + + prevFrame = new uint32[width * height]; + nextFrame = new uint32[width * height]; + memset(prevFrame, 0, width * height * sizeof(uint32)); + memset(nextFrame, 0, width * height * sizeof(uint32)); + + codebook[0].blocks = + codebook[1].blocks = + codebook[2].blocks = NULL; + + curVideoPos = curVideoChunk = 0; + curAudioPos = curAudioChunk = 0; + + prevSample.L = prevSample.R = 0; + } + + virtual ~Escape124() { + delete[] chunks; + delete[] codebook[0].blocks; + delete[] codebook[1].blocks; + delete[] codebook[2].blocks; + delete[] prevFrame; + delete[] nextFrame; + } + + void skipLine() { + char c; + while (stream->read(c) != '\n'); + } + + int readValue() { + char buf[255]; + for (int i = 0; i < sizeof(buf); i++) { + char &c = buf[i]; + stream->read(c); + if (c == ' ' || c == '.' || c == ',' || c == ';' || c == '\n') { + if (c == ' ' || c == '.') + skipLine(); + c = '\0'; + return atoi(buf); + } + } + ASSERT(false); + return 0; + } + + void copySuperBlock(uint32 *dst, int dstWidth, uint32 *src, int srcWidth) { + for (int i = 0; i < 8; i++) { + memcpy(dst, src, 8 * sizeof(uint32)); + src += srcWidth; + dst += dstWidth; + } + } + + int getSkipCount(BitStream &bs) { + int value; + + value = bs.read(1); + if (!value) + return value; + + value += bs.read(3); + if (value != (1 + ((1 << 3) - 1))) + return value; + + value += bs.read(7); + if (value != (1 + ((1 << 3) - 1)) + ((1 << 7) - 1)) + return value; + + return value + bs.read(12); + } + + void decodeMacroBlock(BitStream &bs, MacroBlock &mb, int &cbIndex, int sbIndex) { + int value = bs.read(1); + if (value) { + static const int8 trans[3][2] = { {2, 1}, {0, 2}, {1, 0} }; + value = bs.read(1); + cbIndex = trans[cbIndex][value]; + } + + Codebook &cb = codebook[cbIndex]; + uint32 bIndex = bs.read(cb.depth); + + if (cbIndex == 1) + bIndex += sbIndex << cb.depth; + + memcpy(&mb, cb.blocks + bIndex, sizeof(mb)); + } + + void insertMacroBlock(SuperBlock &sb, const MacroBlock &mb, int index) { + uint32 *dst = sb.pixels + (index + (index & -4)) * 2; + dst[0] = mb.pixels[0]; + dst[1] = mb.pixels[1]; + dst[8] = mb.pixels[2]; + dst[9] = mb.pixels[3]; + } + + union Color32 { + uint32 value; + struct { uint8 r, g, b, a; }; + + Color32() {} + + Color32(uint16 v) { + r = (v & 0x7C00) >> 7; + g = (v & 0x03E0) >> 2; + b = (v & 0x001F) << 3; + a = 255; + } + }; + + bool decode(uint8 *frame) { + if (curVideoChunk >= chunksCount) + return false; + + if (curVideoPos >= chunks[curVideoChunk].videoSize) { + curVideoChunk++; + curVideoPos = 0; + if (curVideoChunk >= chunksCount) + return false; + } + stream->setPos(chunks[curVideoChunk].offset + curVideoPos); + + uint32 flags, size; + stream->read(flags); + stream->read(size); + + curVideoPos += size; + + // skip unchanged frame + if (!(flags & 0x114) || !(flags & 0x7800000)) + return true; + + int sbCount = (width / 8) * (height / 8); + + // read data into bit stream + size -= (sizeof(flags) + sizeof(size)); + + uint8 *data = new uint8[size]; + stream->raw(data, size); + BitStream bs(data, size); + + // read codebook changes + for (int i = 0; i < 3; i++) { + if (flags & (1 << (17 + i))) { + + Codebook &cb = codebook[i]; + + if (i == 2) { + cb.size = bs.read(20); + cb.depth = log2i(cb.size - 1) + 1; + } else { + cb.depth = bs.read(4); + cb.size = (i == 0 ? 1 : sbCount) << cb.depth; + } + + delete[] cb.blocks; + cb.blocks = new MacroBlock[cb.size]; + + for (uint32 j = 0; j < cb.size; j++) { + uint8 mask = bs.read(4); + Color32 cA = Color32(uint16(bs.read(15))); + Color32 cB = Color32(uint16(bs.read(15))); + + if (cA.value != cB.value && (mask == 6 || mask == 9) && // check for 0101 or 1010 mask + abs(int(cA.r) - int(cB.r)) <= 8 && + abs(int(cA.g) - int(cB.g)) <= 8 && + abs(int(cA.b) - int(cB.b)) <= 8) { + + cA.r = (int(cA.r) + int(cB.r)) / 2; + cA.g = (int(cA.g) + int(cB.g)) / 2; + cA.b = (int(cA.b) + int(cB.b)) / 2; + + cB = cA; + } + + for (int k = 0; k < 4; k++) + cb.blocks[j].pixels[k] = (mask & (1 << k)) ? cB.value : cA.value; + } + } + } + + static const uint16 maskMatrix[] = { 0x1, 0x2, 0x10, 0x20, + 0x4, 0x8, 0x40, 0x80, + 0x100, 0x200, 0x1000, 0x2000, + 0x400, 0x800, 0x4000, 0x8000}; + + SuperBlock sb; + MacroBlock mb; + int cbIndex = 1; + + int skip = -1; + for (int sbIndex = 0; sbIndex < sbCount; sbIndex++) { + int sbLine = width / 8; + int sbOffset = ((sbIndex / sbLine) * width + (sbIndex % sbLine)) * 8; + uint32 *src = prevFrame + sbOffset; + uint32 *dst = nextFrame + sbOffset; + + uint16 multiMask = 0; + + if (skip == -1) + skip = getSkipCount(bs); + + if (skip) { + copySuperBlock(dst, width, src, width); + } else { + copySuperBlock(sb.pixels, 8, src, width); + + while (!bs.read(1)) { + decodeMacroBlock(bs, mb, cbIndex, sbIndex); + uint16 mask = bs.read(16); + multiMask |= mask; + for (int i = 0; i < 16; i++) + if (mask & maskMatrix[i]) + insertMacroBlock(sb, mb, i); + } + + if (!bs.read(1)) { + uint16 invMask = bs.read(4); + for (int i = 0; i < 4; i++) + multiMask ^= ((invMask & (1 << i)) ? 0x0F : bs.read(4)) << (i * 4); + for (int i = 0; i < 16; i++) + if (multiMask & maskMatrix[i]) { + decodeMacroBlock(bs, mb, cbIndex, sbIndex); + insertMacroBlock(sb, mb, i); + } + } else + if (flags & (1 << 16)) + while (!bs.read(1)) { + decodeMacroBlock(bs, mb, cbIndex, sbIndex); + insertMacroBlock(sb, mb, bs.read(4)); + } + + copySuperBlock(dst, width, sb.pixels, 8); + } + + skip--; + } + + delete[] data; + + memcpy(frame, nextFrame, width * height * 4); + swap(prevFrame, nextFrame); + + return true; + } + + virtual int decode(Sound::Frame *frames, int count) { + if (abs(curAudioChunk - curVideoChunk) > 1) { // sync with video chunk + curAudioChunk = curVideoChunk; + curAudioPos = 0; + } + + if (curAudioChunk >= chunksCount) { + for (int i = 0; i < count; i++) + frames[i].L = frames[i].R = 0; + return count; + } + + Chunk *chunk = &chunks[curAudioChunk]; + stream->setPos(chunk->offset + chunk->videoSize + curAudioPos); + + int sampleSize = channels * (bps / 8); + + for (int i = 0; i < count; i++) { + + if (curAudioChunk >= chunksCount) { + frames[i].L = frames[i].R = 0; + continue; + } + + if (sfmt == 101) { + ubyte2 sample; + stream->raw(&sample, sizeof(sample)); + frames[i].L = uint16(sample.x) << 7; + frames[i].R = uint16(sample.y) << 7; + } else if (sfmt == 1) { + Sound::Frame sample; + + stream->raw(&sample, sizeof(Sound::Frame)); + + frames[i].L = (int(prevSample.L) + int(sample.L)) / 2; + frames[i].R = (int(prevSample.R) + int(sample.R)) / 2; + i++; + frames[i] = prevSample = sample; + } + + curAudioPos += sampleSize; + if (curAudioPos >= chunk->audioSize) { + curAudioPos = 0; + curAudioChunk++; + if (curAudioChunk < chunksCount) { + chunk = &chunks[curAudioChunk]; + stream->setPos(chunk->offset + chunk->videoSize); + } + } + } + + return count; + } + }; + + Decoder *decoder; + Texture *frameTex[2]; + uint8 *frameData; + float time, step; + float invFPS; + bool isPlaying; + bool needUpdate; + Sound::Sample *sample; + + Video(Stream *stream) : decoder(NULL), time(0.0f), isPlaying(false) { + frameTex[0] = frameTex[1] = NULL; + + if (!stream) return; + + decoder = new Escape124(stream); + frameData = new uint8[decoder->width * decoder->height * 4]; + memset(frameData, 0, decoder->width * decoder->height * 4); + + for (int i = 0; i < 2; i++) + frameTex[i] = new Texture(decoder->width, decoder->height, FMT_RGBA, 0, frameData); + + sample = Sound::play(decoder); + + step = 1.0f / decoder->fps; + time = step; + isPlaying = true; + } + + ~Video() { + sample->decoder = NULL; + sample->stop(); + delete decoder; + delete frameTex[0]; + delete frameTex[1]; + delete[] frameData; + } + + void update() { + if (!isPlaying) return; + + time += Core::deltaTime; + if (time < step) + return; + time -= step; + + isPlaying = needUpdate = decoder->decode(frameData); + } + + void render() { // just update GPU texture if it's necessary + if (!needUpdate) return; + frameTex[0]->update(frameData); + swap(frameTex[0], frameTex[1]); + needUpdate = false; + } +}; + +#endif