1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-08-23 21:32:53 +02:00

#129 TR1 FMVs support (Escape-124)

This commit is contained in:
XProger
2018-07-05 03:29:30 +03:00
parent cd62dba110
commit 61859dc83a
13 changed files with 713 additions and 89 deletions

View File

@@ -587,7 +587,7 @@ namespace Core {
#include "texture.h"
#include "shader.h"
//#include "video.h"
#include "video.h"
namespace Core {

View File

@@ -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;

View File

@@ -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))

View File

@@ -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() {

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -225,6 +225,7 @@
<ClInclude Include="..\..\format.h" />
<ClInclude Include="..\..\trigger.h" />
<ClInclude Include="..\..\utils.h" />
<ClInclude Include="..\..\video.h" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\shaders\filter.glsl" />

View File

@@ -52,6 +52,7 @@
<ClInclude Include="..\..\libs\tinf\tinf.h">
<Filter>libs\tinf</Filter>
</ClInclude>
<ClInclude Include="..\..\video.h" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\shaders\filter.glsl">

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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);

View File

@@ -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,9 +1293,11 @@ struct Stream {
}
void setPos(int pos) {
if (this->pos != pos) {
this->pos = pos;
if (f) fseek(f, pos, SEEK_SET);
}
}
void seek(int offset) {
if (!offset) return;
@@ -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 <typename T>
@@ -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;

437
src/video.h Normal file
View File

@@ -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