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