1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-08-09 14:47:02 +02:00

#11 inventory screen; #27 unified controls preset (old school as default)

This commit is contained in:
XProger
2017-06-19 04:42:00 +03:00
parent 65b73febbc
commit bbf6b8a3c5
25 changed files with 1412 additions and 606 deletions

View File

@@ -70,6 +70,9 @@ struct ShaderCache {
} }
compile(Core::passFilter, Shader::FILTER_DOWNSAMPLE, FX_NONE); compile(Core::passFilter, Shader::FILTER_DOWNSAMPLE, FX_NONE);
compile(Core::passFilter, Shader::FILTER_GRAYSCALE, FX_NONE);
compile(Core::passFilter, Shader::FILTER_BLUR, FX_NONE);
compile(Core::passFilter, Shader::FILTER_MIXER, FX_NONE);
compile(Core::passCompose, Shader::ROOM, FX_NONE); compile(Core::passCompose, Shader::ROOM, FX_NONE);
compile(Core::passCompose, Shader::ROOM, FX_ALPHA_TEST); compile(Core::passCompose, Shader::ROOM, FX_ALPHA_TEST);
@@ -153,7 +156,7 @@ struct ShaderCache {
break; break;
} }
case Core::passFilter : { case Core::passFilter : {
static const char *typeNames[] = { "DEFAULT", "DOWNSAMPLE" }; static const char *typeNames[] = { "DEFAULT", "DOWNSAMPLE", "GRAYSCALE", "BLUR", "MIXER" };
src = FILTER; src = FILTER;
typ = typeNames[type]; typ = typeNames[type];
sprintf(def, "%s#define PASS_%s\n#define FILTER_%s\n", ext, passNames[pass], typ); sprintf(def, "%s#define PASS_%s\n#define FILTER_%s\n", ext, passNames[pass], typ);
@@ -654,9 +657,8 @@ struct WaterCache {
Item &item = items[i]; Item &item = items[i];
if (!item.visible) continue; if (!item.visible) continue;
item.mask->bind(sMask);
if (item.timer >= SIMULATE_TIMESTEP || dropCount) { if (item.timer >= SIMULATE_TIMESTEP || dropCount) {
item.mask->bind(sMask);
// add water drops // add water drops
drop(item); drop(item);
// simulation step // simulation step

View File

@@ -277,7 +277,8 @@ struct Camera : Controller {
} }
mat4 getProjMatrix() { mat4 getProjMatrix() {
return mat4(fov, Core::viewport.z / Core::viewport.w, znear, zfar); return mat4(fov, float(Core::width) / float(Core::height), znear, zfar);
//return mat4(fov, Core::viewport.z / Core::viewport.w, znear, zfar);
} }
virtual void setup(bool calcMatrices) { virtual void setup(bool calcMatrices) {

View File

@@ -26,9 +26,15 @@ struct IGame {
virtual void updateParams() {} virtual void updateParams() {}
virtual void waterDrop(const vec3 &pos, float radius, float strength) {} virtual void waterDrop(const vec3 &pos, float radius, float strength) {}
virtual void setShader(Core::Pass pass, Shader::Type type, bool underwater = false, bool alphaTest = false) {} virtual void setShader(Core::Pass pass, Shader::Type type, bool underwater = false, bool alphaTest = false) {}
virtual void setupBinding() {}
virtual void renderEnvironment(int roomIndex, const vec3 &pos, Texture **targets, int stride = 0) {} virtual void renderEnvironment(int roomIndex, const vec3 &pos, Texture **targets, int stride = 0) {}
virtual void renderCompose(int roomIndex, bool genShadowMask = false) {} virtual void renderCompose(int roomIndex, bool genShadowMask = false) {}
virtual void fxQuake(float time) {} virtual void fxQuake(float time) {}
virtual bool invUse(TR::Entity::Type item, TR::Entity::Type slot) { return false; }
virtual void invAdd(TR::Entity::Type type, int count = 1) {}
virtual Sound::Sample* playSound(int id, const vec3 &pos, int flags, int group = -1) const { return NULL; }
}; };
struct Controller { struct Controller {
@@ -181,25 +187,7 @@ struct Controller {
} }
Sound::Sample* playSound(int id, const vec3 &pos, int flags) const { Sound::Sample* playSound(int id, const vec3 &pos, int flags) const {
if (level->version == TR::Level::VER_TR1_PSX && id == TR::SND_SECRET) return game->playSound(id, pos, flags, entity);
return NULL;
int16 a = level->soundsMap[id];
if (a == -1) return NULL;
TR::SoundInfo &b = level->soundsInfo[a];
if (b.chance == 0 || (rand() & 0x7fff) <= b.chance) {
int index = b.offset + rand() % b.flags.count;
float volume = (float)b.volume / 0x7FFF;
float pitch = b.flags.pitch ? (0.9f + randf() * 0.2f) : 1.0f;
if (b.flags.mode == 1) flags |= Sound::UNIQUE;
//if (b.flags.mode == 2) flags |= Sound::REPLAY;
if (b.flags.mode == 3) flags |= Sound::SYNC;
if (b.flags.gain) volume = max(0.0f, volume - randf() * 0.25f);
if (b.flags.fixed) flags |= Sound::LOOP;
return Sound::play(level->getSampleStream(index), pos, volume, pitch, flags, entity * 1000 + index);
}
return NULL;
} }
vec3 getDir() const { vec3 getDir() const {

View File

@@ -123,6 +123,11 @@
#define glActiveStencilFaceEXT(...) #define glActiveStencilFaceEXT(...)
#endif #endif
namespace Core {
float deltaTime;
int width, height;
}
#include "utils.h" #include "utils.h"
#include "input.h" #include "input.h"
#include "sound.h" #include "sound.h"
@@ -290,8 +295,6 @@ enum BlendMode { bmNone, bmAlpha, bmAdd, bmMultiply, bmScreen };
extern int getTime(); extern int getTime();
namespace Core { namespace Core {
int width, height;
float deltaTime;
float eye; float eye;
vec4 viewport, viewportDef; vec4 viewport, viewportDef;
vec4 scissor; vec4 scissor;
@@ -307,6 +310,8 @@ namespace Core {
enum Pass { passCompose, passShadow, passAmbient, passWater, passFilter, passVolume, passGUI, passMAX } pass; enum Pass { passCompose, passShadow, passAmbient, passWater, passFilter, passVolume, passGUI, passMAX } pass;
GLuint FBO, defaultFBO; GLuint FBO, defaultFBO;
Texture *defaultTarget;
struct RenderTargetCache { struct RenderTargetCache {
int count; int count;
struct Item { struct Item {
@@ -322,6 +327,8 @@ namespace Core {
Texture *target; Texture *target;
int targetFace; int targetFace;
GLuint VAO; GLuint VAO;
GLuint iBuffer;
GLuint vBuffer;
BlendMode blendMode; BlendMode blendMode;
CullMode cullMode; CullMode cullMode;
bool stencilTwoSide; bool stencilTwoSide;
@@ -371,7 +378,7 @@ namespace Core {
} }
void init() { void init() {
Input::reset(); Input::init();
#ifdef ANDROID #ifdef ANDROID
void *libGL = dlopen("libGLESv2.so", RTLD_LAZY); void *libGL = dlopen("libGLESv2.so", RTLD_LAZY);
#endif #endif
@@ -499,6 +506,7 @@ namespace Core {
glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&defaultFBO); glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&defaultFBO);
glGenFramebuffers(1, &FBO); glGenFramebuffers(1, &FBO);
memset(rtCache, 0, sizeof(rtCache)); memset(rtCache, 0, sizeof(rtCache));
defaultTarget = NULL;
Sound::init(); Sound::init();
@@ -564,9 +572,13 @@ namespace Core {
glClearStencil(value); glClearStencil(value);
} }
void setViewport(const vec4 &vp) {
glViewport(int(vp.x), int(vp.y), int(vp.z), int(vp.w));
viewport = vp;
}
void setViewport(int x, int y, int width, int height) { void setViewport(int x, int y, int width, int height) {
glViewport(x, y, width, height); setViewport(vec4(float(x), float(y), float(width), float(height)));
viewport = vec4(float(x), float(y), float(width), float(height));
} }
void setScissor(int x, int y, int width, int height) { void setScissor(int x, int y, int width, int height) {
@@ -699,16 +711,17 @@ namespace Core {
} }
void setTarget(Texture *target, bool clear = false, int face = 0) { void setTarget(Texture *target, bool clear = false, int face = 0) {
if (target == active.target && face == active.targetFace) if (!target && defaultTarget)
return; target = defaultTarget;
if (target != active.target || face != active.targetFace) {
if (!target) { if (!target) {
glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO); glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
glColorMask(true, true, true, true); glColorMask(true, true, true, true);
setViewport(int(viewportDef.x), int(viewportDef.y), int(viewportDef.z), int(viewportDef.w)); setViewport(int(viewportDef.x), int(viewportDef.y), int(viewportDef.z), int(viewportDef.w));
} else { } else {
if (active.target == NULL) if (active.target == NULL || active.target == defaultTarget)
viewportDef = viewport; viewportDef = viewport;
GLenum texTarget = GL_TEXTURE_2D; GLenum texTarget = GL_TEXTURE_2D;
if (target->cube) if (target->cube)
@@ -723,8 +736,11 @@ namespace Core {
if (depth) if (depth)
glColorMask(false, false, false, false); glColorMask(false, false, false, false);
else
glColorMask(true, true, true, true);
setViewport(0, 0, target->width, target->height); setViewport(0, 0, target->width, target->height);
} }
}
if (clear) if (clear)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

View File

@@ -80,7 +80,7 @@
E( BRIDGE_0 ) \ E( BRIDGE_0 ) \
E( BRIDGE_1 ) \ E( BRIDGE_1 ) \
E( BRIDGE_2 ) \ E( BRIDGE_2 ) \
E( INV_GAME ) \ E( INV_PASSPORT ) \
E( INV_COMPASS ) \ E( INV_COMPASS ) \
E( INV_HOME ) \ E( INV_HOME ) \
E( GEARS_1 ) \ E( GEARS_1 ) \
@@ -90,7 +90,7 @@
E( CUT_2 ) \ E( CUT_2 ) \
E( CUT_3 ) \ E( CUT_3 ) \
E( CUT_4 ) \ E( CUT_4 ) \
E( INV_GAME_CLOSED ) \ E( INV_PASSPORT_CLOSED ) \
E( INV_MAP ) \ E( INV_MAP ) \
E( CRYSTAL ) \ E( CRYSTAL ) \
E( WEAPON_PISTOLS ) \ E( WEAPON_PISTOLS ) \
@@ -107,12 +107,12 @@
E( INV_DETAIL ) \ E( INV_DETAIL ) \
E( INV_SOUND ) \ E( INV_SOUND ) \
E( INV_CONTROLS ) \ E( INV_CONTROLS ) \
E( INV_FLASHLIGHT ) \ E( INV_GAMMA ) \
E( INV_PISTOLS ) \ E( INV_PISTOLS ) \
E( INV_SHOTGUN ) \ E( INV_SHOTGUN ) \
E( INV_MAGNUMS ) \ E( INV_MAGNUMS ) \
E( INV_UZIS ) \ E( INV_UZIS ) \
E( INV_AMMO_POSTOLS ) \ E( INV_AMMO_PISTOLS ) \
E( INV_AMMO_SHOTGUN ) \ E( INV_AMMO_SHOTGUN ) \
E( INV_AMMO_MAGNUMS ) \ E( INV_AMMO_MAGNUMS ) \
E( INV_AMMO_UZIS ) \ E( INV_AMMO_UZIS ) \
@@ -291,14 +291,14 @@ namespace TR {
SND_UNDERWATER = 60, SND_UNDERWATER = 60,
SND_MENU_SPIN = 108, SND_INV_SPIN = 108,
SND_MENU_HOME = 109, SND_INV_HOME = 109,
SND_MENU_CONTROLS = 110, SND_INV_CONTROLS = 110,
SND_MENU_SHOW = 111, SND_INV_SHOW = 111,
SND_MENU_HIDE = 112, SND_INV_HIDE = 112,
SND_MENU_COMPASS = 113, SND_INV_COMPASS = 113,
SND_MENU_WEAPON = 114, SND_INV_WEAPON = 114,
SND_MENU_PAGE = 115, SND_INV_PAGE = 115,
SND_HEALTH = 116, SND_HEALTH = 116,
SND_DART = 151, SND_DART = 151,
@@ -987,6 +987,29 @@ namespace TR {
int16 puzzleSet; int16 puzzleSet;
int16 weapons[4]; int16 weapons[4];
int16 braid; int16 braid;
struct {
int16 passport;
int16 passport_closed;
int16 map;
int16 compass;
int16 home;
int16 detail;
int16 sound;
int16 controls;
int16 gamma;
int16 weapon[4];
int16 ammo[4];
int16 medikit[2];
int16 puzzle[4];
int16 key[4];
int16 leadbar;
int16 scion;
} inv;
int16 glyphSeq;
} extra; } extra;
Level(Stream &stream, bool demo) { Level(Stream &stream, bool demo) {
@@ -1201,10 +1224,7 @@ namespace TR {
// init secrets states // init secrets states
memset(secrets, 0, MAX_SECRETS_COUNT * sizeof(secrets[0])); memset(secrets, 0, MAX_SECRETS_COUNT * sizeof(secrets[0]));
// get special models indices // get special models indices
memset(&extra, 0, sizeof(extra)); memset(&extra, 0xFF, sizeof(extra));
for (int i = 0; i < 4; i++)
extra.weapons[i] = -1;
extra.braid = -1;
for (int i = 0; i < modelsCount; i++) for (int i = 0; i < modelsCount; i++)
switch (models[i].type) { switch (models[i].type) {
@@ -1215,8 +1235,53 @@ namespace TR {
case Entity::LARA_MAGNUMS : extra.weapons[2] = i; break; case Entity::LARA_MAGNUMS : extra.weapons[2] = i; break;
case Entity::LARA_UZIS : extra.weapons[3] = i; break; case Entity::LARA_UZIS : extra.weapons[3] = i; break;
case Entity::BRAID : extra.braid = i; break; case Entity::BRAID : extra.braid = i; break;
case Entity::INV_PASSPORT : extra.inv.passport = i; break;
case Entity::INV_PASSPORT_CLOSED : extra.inv.passport_closed = i; break;
case Entity::INV_MAP : extra.inv.map = i; break;
case Entity::INV_COMPASS : extra.inv.compass = i; break;
case Entity::INV_HOME : extra.inv.home = i; break;
case Entity::INV_DETAIL : extra.inv.detail = i; break;
case Entity::INV_SOUND : extra.inv.sound = i; break;
case Entity::INV_CONTROLS : extra.inv.controls = i; break;
case Entity::INV_GAMMA : extra.inv.gamma = i; break;
case Entity::INV_PISTOLS : extra.inv.weapon[0] = i; break;
case Entity::INV_SHOTGUN : extra.inv.weapon[1] = i; break;
case Entity::INV_MAGNUMS : extra.inv.weapon[2] = i; break;
case Entity::INV_UZIS : extra.inv.weapon[3] = i; break;
case Entity::INV_AMMO_PISTOLS : extra.inv.ammo[0] = i; break;
case Entity::INV_AMMO_SHOTGUN : extra.inv.ammo[1] = i; break;
case Entity::INV_AMMO_MAGNUMS : extra.inv.ammo[2] = i; break;
case Entity::INV_AMMO_UZIS : extra.inv.ammo[3] = i; break;
case Entity::INV_MEDIKIT_SMALL : extra.inv.medikit[0] = i; break;
case Entity::INV_MEDIKIT_BIG : extra.inv.medikit[1] = i; break;
case Entity::INV_PUZZLE_1 : extra.inv.puzzle[0] = i; break;
case Entity::INV_PUZZLE_2 : extra.inv.puzzle[1] = i; break;
case Entity::INV_PUZZLE_3 : extra.inv.puzzle[2] = i; break;
case Entity::INV_PUZZLE_4 : extra.inv.puzzle[3] = i; break;
case Entity::INV_KEY_1 : extra.inv.key[0] = i; break;
case Entity::INV_KEY_2 : extra.inv.key[1] = i; break;
case Entity::INV_KEY_3 : extra.inv.key[2] = i; break;
case Entity::INV_KEY_4 : extra.inv.key[3] = i; break;
case Entity::INV_LEADBAR : extra.inv.leadbar = i; break;
case Entity::INV_SCION : extra.inv.scion = i; break;
default : ; default : ;
} }
for (int i = 0; i < spriteSequencesCount; i++)
if (spriteSequences[i].type == TR::Entity::GLYPH) {
extra.glyphSeq = i;
break;
}
ASSERT(extra.glyphSeq != -1);
// init cutscene transform // init cutscene transform
cutMatrix.identity(); cutMatrix.identity();
if (cutEntity > -1) { if (cutEntity > -1) {
@@ -1665,7 +1730,7 @@ namespace TR {
return Color24(255, 0, 255); return Color24(255, 0, 255);
} }
Stream* getSampleStream(int index) { Stream* getSampleStream(int index) const {
uint8 *data = &soundData[soundOffsets[index]]; uint8 *data = &soundData[soundOffsets[index]];
uint32 size = 0; uint32 size = 0;
switch (version) { switch (version) {

View File

@@ -8,13 +8,11 @@
namespace Game { namespace Game {
Level *level; Level *level;
UI *ui;
void startLevel(Stream *lvl, Stream *snd, bool demo, bool home) { void startLevel(Stream *lvl, Stream *snd, bool demo, bool home) {
delete ui;
delete level; delete level;
level = new Level(*lvl, snd, demo, home); level = new Level(*lvl, snd, demo, home);
ui = new UI(level); UI::init(level);
delete lvl; delete lvl;
} }
@@ -27,7 +25,6 @@ namespace Game {
Core::settings.water = Core::support.texFloat || Core::support.texHalf; Core::settings.water = Core::support.texFloat || Core::support.texHalf;
level = NULL; level = NULL;
ui = NULL;
startLevel(lvl, snd, false, false); startLevel(lvl, snd, false, false);
} }
@@ -40,12 +37,14 @@ namespace Game {
} }
void free() { void free() {
delete ui;
delete level; delete level;
Core::free(); Core::free();
} }
void updateTick() { void updateTick() {
if (Input::state[cInventory])
level->inventory.toggle();
float dt = Core::deltaTime; float dt = Core::deltaTime;
if (Input::down[ikR]) // slow motion (for animation debugging) if (Input::down[ikR]) // slow motion (for animation debugging)
Core::deltaTime /= 10.0f; Core::deltaTime /= 10.0f;
@@ -60,13 +59,15 @@ namespace Game {
} }
void update(float delta) { void update(float delta) {
Input::update();
if (Input::down[ikV]) { // third <-> first person view if (Input::down[ikV]) { // third <-> first person view
level->camera->changeView(!level->camera->firstPerson); level->camera->changeView(!level->camera->firstPerson);
Input::down[ikV] = false; Input::down[ikV] = false;
} }
Core::deltaTime = delta = min(1.0f, delta); Core::deltaTime = delta = min(1.0f, delta);
ui->update(); UI::update();
while (delta > EPS) { while (delta > EPS) {
Core::deltaTime = min(delta, 1.0f / 30.0f); Core::deltaTime = min(delta, 1.0f / 30.0f);
@@ -79,7 +80,12 @@ namespace Game {
PROFILE_TIMING(Core::stats.tFrame); PROFILE_TIMING(Core::stats.tFrame);
Core::beginFrame(); Core::beginFrame();
level->render(); level->render();
ui->renderTouch(); UI::renderTouch();
#ifdef _DEBUG
level->renderDebug();
#endif
Core::endFrame(); Core::endFrame();
} }
} }

View File

@@ -5,7 +5,7 @@
enum InputKey { ikNone, enum InputKey { ikNone,
// keyboard // keyboard
ikLeft, ikRight, ikUp, ikDown, ikSpace, ikEnter, ikEscape, ikShift, ikCtrl, ikAlt, ikLeft, ikRight, ikUp, ikDown, ikSpace, ikTab, ikEnter, ikEscape, ikShift, ikCtrl, ikAlt,
ik0, ik1, ik2, ik3, ik4, ik5, ik6, ik7, ik8, ik9, ik0, ik1, ik2, ik3, ik4, ik5, ik6, ik7, ik8, ik9,
ikA, ikB, ikC, ikD, ikE, ikF, ikG, ikH, ikI, ikJ, ikK, ikL, ikM, ikA, ikB, ikC, ikD, ikE, ikF, ikG, ikH, ikI, ikJ, ikK, ikL, ikM,
ikN, ikO, ikP, ikQ, ikR, ikS, ikT, ikU, ikV, ikW, ikX, ikY, ikZ, ikN, ikO, ikP, ikQ, ikR, ikS, ikT, ikU, ikV, ikW, ikX, ikY, ikZ,
@@ -15,12 +15,55 @@ enum InputKey { ikNone,
ikTouchA, ikTouchB, ikTouchC, ikTouchD, ikTouchE, ikTouchF, ikTouchA, ikTouchB, ikTouchC, ikTouchD, ikTouchE, ikTouchF,
// gamepad // gamepad
ikJoyA, ikJoyB, ikJoyX, ikJoyY, ikJoyLB, ikJoyRB, ikJoySelect, ikJoyStart, ikJoyL, ikJoyR, ikJoyLT, ikJoyRT, ikJoyPOV, ikJoyA, ikJoyB, ikJoyX, ikJoyY, ikJoyLB, ikJoyRB, ikJoySelect, ikJoyStart, ikJoyL, ikJoyR, ikJoyLT, ikJoyRT, ikJoyPOV,
ikJoyLeft, ikJoyRight, ikJoyUp, ikJoyDown,
ikMAX }; ikMAX };
enum ControlKey { cLeft, cRight, cUp, cDown, cJump, cWalk, cAction, cWeapon, cLook, cStepLeft, cStepRight, cRoll, cInventory, cMAX };
namespace Input { namespace Input {
bool down[ikMAX]; bool down[ikMAX];
struct KeySet {
InputKey key, joy;
};
static const KeySet presets[1][cMAX] = {
{ { ikLeft, ikJoyLeft },
{ ikRight, ikJoyRight },
{ ikUp, ikJoyUp },
{ ikDown, ikJoyDown },
{ ikAlt, ikJoyX },
{ ikShift, ikJoyRB },
{ ikCtrl, ikJoyA },
{ ikSpace, ikJoyY },
{ ikC, ikJoyLB },
{ ikZ, ikJoyLT },
{ ikX, ikJoyRT },
{ ikA, ikJoyB },
{ ikTab, ikJoySelect },
},
/*
{ { ikA, ikJoyLeft },
{ ikD, ikJoyRight },
{ ikW, ikJoyUp },
{ ikS, ikJoyDown },
{ ikSpace, ikJoyX },
{ ikShift, ikJoyRB },
{ ikP, ikJoyA },
{ ikMouseM, ikJoyY },
{ ikMouseR, ikJoyLB },
{ ikLeft, ikJoyLT },
{ ikRight, ikJoyRT },
{ ikUp, ikJoyB },
{ ikTab, ikJoySelect },
},
*/
};
KeySet controls[cMAX];
bool state[cMAX];
struct { struct {
vec2 pos; vec2 pos;
struct { struct {
@@ -68,13 +111,15 @@ namespace Input {
} }
} head; } head;
void reset() { enum TouchButton { bNone, bWeapon, bWalk, bAction, bJump, bMAX };
memset(down, 0, sizeof(down)); enum TouchZone { zMove, zLook, zButton, zMAX };
memset(&mouse, 0, sizeof(mouse));
memset(&joy, 0, sizeof(joy)); float touchTimerVis, touchTimerTap;
memset(&touch, 0, sizeof(touch)); InputKey touchKey[zMAX];
head.reset(); TouchButton btn;
} vec2 btnPos[bMAX];
float btnRadius;
bool doubleTap;
void setDown(InputKey key, bool value) { void setDown(InputKey key, bool value) {
if (down[key] == value) if (down[key] == value)
@@ -130,6 +175,147 @@ namespace Input {
return ikNone; return ikNone;
} }
void reset() {
memset(down, 0, sizeof(down));
memset(&mouse, 0, sizeof(mouse));
memset(&joy, 0, sizeof(joy));
memset(&touch, 0, sizeof(touch));
head.reset();
}
void init() {
reset();
for (int i = 0; i < cMAX; i++)
controls[i] = presets[0][i];
touchTimerVis = 0.0f;
touchTimerTap = 0.0f;
doubleTap = false;
touchKey[zMove] = touchKey[zLook] = touchKey[zButton] = ikNone;
}
bool checkTouchZone(TouchZone zone) {
InputKey &t = touchKey[zone];
if (t != ikNone && !down[t]) {
t = ikNone;
return true;
}
return false;
}
void getTouchDir(InputKey key, vec2 &dir) {
vec2 delta = vec2(0.0f);
if (key == ikNone)
return;
Touch &t = touch[key - ikTouchA];
vec2 d = t.pos - t.start;
float len = d.length();
if (len > EPS)
delta = d * (min(len / 100.0f, 1.0f) / len);
dir = delta;
}
void update() {
int p = joy.POV;
setDown(ikJoyUp, p == 8 || p == 1 || p == 2);
setDown(ikJoyRight, p == 2 || p == 3 || p == 4);
setDown(ikJoyDown, p == 4 || p == 5 || p == 6);
setDown(ikJoyLeft, p == 6 || p == 7 || p == 8);
for (int i = 0; i < cMAX; i++) {
KeySet &c = controls[i];
state[i] = (c.key != ikNone && down[c.key]) || (c.joy != ikNone && down[c.joy]);
}
// update touch controls
if (touchTimerTap > 0.0f)
touchTimerTap = max(0.0f, touchTimerTap - Core::deltaTime);
if (touchKey[zMove] != ikNone || touchKey[zLook] != ikNone || touchKey[zButton] != ikNone)
touchTimerVis = 30.0f;
else
if (touchTimerVis > 0.0f)
touchTimerVis = max(0.0f, touchTimerVis - Core::deltaTime);
// update buttons
float offset = Core::height * 0.25f;
float radius = offset;
vec2 center = vec2(Core::width - offset * 0.7f, Core::height - offset * 0.7f);
btnPos[bWeapon] = center;
btnPos[bJump] = center + vec2(cos(-PI * 0.5f), sin(-PI * 0.5f)) * radius;
btnPos[bAction] = center + vec2(cos(-PI * 3.0f / 4.0f), sin(-PI * 3.0f / 4.0f)) * radius;
btnPos[bWalk] = center + vec2(cos(-PI), sin(-PI)) * radius;
btnRadius = Core::height * (25.0f / 1080.0f);
// touch update
if (checkTouchZone(zMove))
joy.L = vec2(0.0f);
if (checkTouchZone(zLook))
joy.R = vec2(0.0f);
if (checkTouchZone(zButton))
btn = bNone;
if (doubleTap)
doubleTap = false;
float zoneSize = Core::width / 3.0f;
for (int i = 0; i < COUNT(touch); i++) {
InputKey key = InputKey(i + ikTouchA);
if (!down[key]) continue;
if (key == touchKey[zMove] || key == touchKey[zLook] || key == touchKey[zButton]) continue;
int zone = clamp(int(touch[i].pos.x / zoneSize), 0, 2);
InputKey &t = touchKey[zone];
if (t == ikNone) {
t = key;
if (zone == zMove) {
if (touchTimerTap > 0.0f && touchTimerTap < 0.3f) { // 100 ms gap to reduce false positives (bad touch sensors)
doubleTap = true;
touchTimerTap = 0.0f;
} else
touchTimerTap = 0.3f;
}
}
}
// set active touches as gamepad controls
getTouchDir(touchKey[zMove], joy.L);
getTouchDir(touchKey[zLook], joy.R);
if (touchKey[zButton] != ikNone) {
vec2 pos = touch[touchKey[zButton] - ikTouchA].pos;
btn = bMAX;
float minDist = btnRadius * 8.0f;
for (int i = 0; i < bMAX; i++) {
float d = (pos - btnPos[i]).length();
if (d < minDist) {
minDist = d;
btn = TouchButton(i);
}
}
switch (btn) {
case bWeapon : state[cWeapon] = true; break;
case bWalk : state[cWalk] = true; break;
case bAction : state[cAction] = true; break;
case bJump : state[cJump] = true; break;
default : ;
}
}
if (doubleTap)
state[cRoll] = true;
}
} }
#endif #endif

View File

@@ -2,18 +2,175 @@
#define H_INVENTORY #define H_INVENTORY
#include "format.h" #include "format.h"
#include "controller.h"
#include "ui.h"
#define MAX_ITEMS 64 #define INVENTORY_MAX_ITEMS 64
#define INVENTORY_MAX_RADIUS 688.0f
#define INVENTORY_BG_SIZE 512
#define INVENTORY_HEIGHT 2048.0f
struct Inventory { struct Inventory {
enum Page {
PAGE_OPTION,
PAGE_INVENTORY,
PAGE_ITEMS,
PAGE_MAX
};
IGame *game;
Texture *background[3];
bool active;
bool chosen;
float phaseRing, phasePage, phaseChoose, phaseSelect;
int index, targetIndex, pageItemIndex[PAGE_MAX];
Page page, targetPage;
int itemsCount;
struct Item { struct Item {
TR::Entity::Type type; TR::Entity::Type type;
int count; int count;
} items[MAX_ITEMS]; float angle;
int itemsCount; Animation *anim;
Inventory() : itemsCount(0) {} struct Desc {
const char *name;
int page;
int model;
} desc;
Item() : anim(NULL) {}
Item(TR::Level *level, TR::Entity::Type type, int count = 1) : type(type), count(count), angle(0.0f) {
switch (type) {
case TR::Entity::INV_PASSPORT : desc = { "Game", 0, level->extra.inv.passport }; break;
case TR::Entity::INV_PASSPORT_CLOSED : desc = { "Game", 0, level->extra.inv.passport_closed }; break;
case TR::Entity::INV_MAP : desc = { "Map", 1, level->extra.inv.map }; break;
case TR::Entity::INV_COMPASS : desc = { "Compass", 1, level->extra.inv.compass }; break;
case TR::Entity::INV_HOME : desc = { "Lara's Home", 0, level->extra.inv.home }; break;
case TR::Entity::INV_DETAIL : desc = { "Detail Levels", 0, level->extra.inv.detail }; break;
case TR::Entity::INV_SOUND : desc = { "Sound", 0, level->extra.inv.sound }; break;
case TR::Entity::INV_CONTROLS : desc = { "Controls", 0, level->extra.inv.controls }; break;
case TR::Entity::INV_GAMMA : desc = { "Gamma", 0, level->extra.inv.gamma }; break;
case TR::Entity::INV_PISTOLS : desc = { "Pistols", 1, level->extra.inv.weapon[0] }; break;
case TR::Entity::INV_SHOTGUN : desc = { "Shotgun", 1, level->extra.inv.weapon[1] }; break;
case TR::Entity::INV_MAGNUMS : desc = { "Magnums", 1, level->extra.inv.weapon[2] }; break;
case TR::Entity::INV_UZIS : desc = { "Uzis", 1, level->extra.inv.weapon[3] }; break;
case TR::Entity::INV_AMMO_PISTOLS : desc = { "Pistol Clips", 1, level->extra.inv.ammo[0] }; break;
case TR::Entity::INV_AMMO_SHOTGUN : desc = { "Shotgun Shells", 1, level->extra.inv.ammo[1] }; break;
case TR::Entity::INV_AMMO_MAGNUMS : desc = { "Magnum Clips", 1, level->extra.inv.ammo[2] }; break;
case TR::Entity::INV_AMMO_UZIS : desc = { "Uzi Clips", 1, level->extra.inv.ammo[3] }; break;
case TR::Entity::INV_MEDIKIT_SMALL : desc = { "Small Medi Pack", 1, level->extra.inv.medikit[0] }; break;
case TR::Entity::INV_MEDIKIT_BIG : desc = { "Large Medi Pack", 1, level->extra.inv.medikit[1] }; break;
case TR::Entity::INV_PUZZLE_1 : desc = { "Puzzle", 2, level->extra.inv.puzzle[0] }; break;
case TR::Entity::INV_PUZZLE_2 : desc = { "Puzzle", 2, level->extra.inv.puzzle[1] }; break;
case TR::Entity::INV_PUZZLE_3 : desc = { "Puzzle", 2, level->extra.inv.puzzle[2] }; break;
case TR::Entity::INV_PUZZLE_4 : desc = { "Puzzle", 2, level->extra.inv.puzzle[3] }; break;
case TR::Entity::INV_KEY_1 : desc = { "Key", 2, level->extra.inv.key[0] }; break;
case TR::Entity::INV_KEY_2 : desc = { "Key", 2, level->extra.inv.key[1] }; break;
case TR::Entity::INV_KEY_3 : desc = { "Key", 2, level->extra.inv.key[2] }; break;
case TR::Entity::INV_KEY_4 : desc = { "Key", 2, level->extra.inv.key[3] }; break;
case TR::Entity::INV_LEADBAR : desc = { "Lead Bar", 2, level->extra.inv.leadbar }; break;
case TR::Entity::INV_SCION : desc = { "Scion", 2, level->extra.inv.scion }; break;
default : desc = { "unknown", 2, -1 }; break;
}
if (desc.model > -1) {
anim = new Animation(level, &level->models[desc.model]);
anim->isEnded = true;
} else
anim = NULL;
}
~Item() {
delete anim;
}
Item& operator = (Item &item) {
memcpy(this, &item, sizeof(item));
item.anim = NULL;
return *this;
}
void reset() {
if (anim) {
anim->setAnim(0, 0, false);
anim->isEnded = true;
}
}
void update() {
if (anim) anim->update();
}
void render(IGame *game, const Basis &basis) {
if (!anim) return;
TR::Level *level = game->getLevel();
TR::Model &m = level->models[desc.model];
Basis joints[34];
anim->getJoints(basis, -1, true, joints);
Core::active.shader->setParam(uBasis, joints[0], m.mCount);
game->getMesh()->renderModel(desc.model);
}
void choose() {
if (anim) anim->setAnim(0, 0, false);
}
} items[INVENTORY_MAX_ITEMS];
Inventory(IGame *game) : game(game), active(false), chosen(false), index(0), targetIndex(0), page(PAGE_OPTION), targetPage(PAGE_OPTION), itemsCount(0) {
TR::Level *level = game->getLevel();
add(TR::Entity::INV_PASSPORT);
add(TR::Entity::INV_DETAIL);
add(TR::Entity::INV_SOUND);
add(TR::Entity::INV_CONTROLS);
add(TR::Entity::INV_COMPASS);
if (level->extra.inv.map)
add(TR::Entity::INV_MAP);
if (level->extra.inv.gamma)
add(TR::Entity::INV_GAMMA);
add(TR::Entity::INV_PISTOLS, 999);
add(TR::Entity::INV_SHOTGUN, 999);
add(TR::Entity::INV_MAGNUMS, 999);
add(TR::Entity::INV_UZIS, 999);
add(TR::Entity::INV_MEDIKIT_SMALL, 999);
add(TR::Entity::INV_MEDIKIT_BIG, 999);
add(TR::Entity::INV_SCION, 1);
add(TR::Entity::INV_KEY_1, 1);
add(TR::Entity::INV_PUZZLE_1, 1);
phaseRing = phasePage = phaseChoose = phaseSelect = 0.0f;
memset(pageItemIndex, 0, sizeof(pageItemIndex));
for (int i = 0; i < COUNT(background); i++)
background[i] = new Texture(INVENTORY_BG_SIZE, INVENTORY_BG_SIZE, Texture::RGBA, false);
}
~Inventory() {
for (int i = 0; i < COUNT(background); i++)
delete background[i];
}
bool isActive() {
return active || phaseRing > 0.0f;
}
int contains(TR::Entity::Type type) { int contains(TR::Entity::Type type) {
for (int i = 0; i < itemsCount; i++) for (int i = 0; i < itemsCount; i++)
@@ -29,11 +186,21 @@ struct Inventory {
return; return;
} }
if(itemsCount < MAX_ITEMS) { ASSERT(itemsCount < INVENTORY_MAX_ITEMS);
items[itemsCount].type = type;
items[itemsCount].count = count; int pos = 0;
itemsCount++; for (int pos = 0; pos < itemsCount; pos++)
if (items[pos].type > type)
break;
if (pos - itemsCount) {
for (int i = itemsCount; i > pos; i--)
items[i] = items[i - 1];
} }
Item it(game->getLevel(), type, count);
items[pos] = it;
itemsCount++;
} }
int getCount(TR::Entity::Type type) { int getCount(TR::Entity::Type type) {
@@ -47,6 +214,364 @@ struct Inventory {
if (i > -1) if (i > -1)
items[i].count -= count; items[i].count -= count;
} }
bool use(TR::Entity::Type item, TR::Entity::Type slot) {
if (item == TR::Entity::NONE) {
switch (slot) {
case TR::Entity::KEY_HOLE_1 : item = TR::Entity::KEY_1; break; // TODO: 1-4
case TR::Entity::PUZZLE_HOLE_1 : item = TR::Entity::PUZZLE_1; break;
default : return false;
}
}
if (getCount(item) > 0) {
remove(item);
return true;
}
return false;
}
bool toggle(Page curPage = PAGE_INVENTORY) {
if (phaseRing == 0.0f || phaseRing == 1.0f) {
active = !active;
vec3 p;
game->playSound(active ? TR::SND_INV_SHOW : TR::SND_INV_HIDE, p, 0, 0);
chosen = false;
if (active) {
for (int i = 0; i < itemsCount; i++)
items[i].reset();
phasePage = 1.0f;
phaseSelect = 1.0f;
page = targetPage = curPage;
index = targetIndex = pageItemIndex[page];
}
}
return active;
}
void doPhase(bool increase, float speed, float &value) {
if (increase) {
if (value < 1.0f) {
value += Core::deltaTime * speed;
if (value > 1.0f)
value = 1.0f;
}
} else {
if (value > 0.0f) {
value -= Core::deltaTime * speed;
if (value < 0.0f)
value = 0.0f;
}
}
}
int getItemIndex(Page page, int index) {
for (int i = 0; i < itemsCount; i++)
if (items[i].desc.page == page) {
if (!index)
return i;
index--;
}
return 0;
}
void update() {
doPhase(active, 2.0f, phaseRing);
doPhase(true, 1.6f, phasePage);
doPhase(chosen, 4.0f, phaseChoose);
doPhase(true, 2.5f, phaseSelect);
if (page != targetPage && phasePage == 1.0f) {
page = targetPage;
index = targetIndex = pageItemIndex[page];
}
if (index != targetIndex && phaseSelect == 1.0f)
index = pageItemIndex[page] = targetIndex;
int count = getItemsCount(page);
bool ready = active && phaseRing == 1.0f && phasePage == 1.0f;
if (index == targetIndex && targetPage == page && ready && !chosen) {
if (Input::state[cLeft]) { phaseSelect = 0.0f; targetIndex = (targetIndex - 1 + count) % count; }
if (Input::state[cRight]) { phaseSelect = 0.0f; targetIndex = (targetIndex + 1) % count; }
if (Input::state[cUp] && page < PAGE_ITEMS && getItemsCount(page + 1)) { phasePage = 0.0f; targetPage = Page(page + 1); }
if (Input::state[cDown] && page > PAGE_OPTION && getItemsCount(page - 1)) { phasePage = 0.0f; targetPage = Page(page - 1); }
if (index != targetIndex) {
vec3 p;
game->playSound(TR::SND_INV_SPIN, p, 0, 0);
}
}
vec3 p;
Item &item = items[getItemIndex(page, index)];
if (index == targetIndex && ready) {
if (Input::state[cAction] && (phaseChoose == 0.0f || (phaseChoose == 1.0f && item.anim->isEnded))) {
chosen = !chosen;
if (!chosen) {
item.angle = 0.0f;
} else {
switch (item.type) {
case TR::Entity::INV_COMPASS : game->playSound(TR::SND_INV_COMPASS, p, 0, 0); break;
case TR::Entity::INV_HOME : game->playSound(TR::SND_INV_HOME, p, 0, 0); break;
case TR::Entity::INV_CONTROLS : game->playSound(TR::SND_INV_CONTROLS, p, 0, 0); break;
case TR::Entity::INV_PISTOLS :
case TR::Entity::INV_SHOTGUN :
case TR::Entity::INV_MAGNUMS :
case TR::Entity::INV_UZIS : game->playSound(TR::SND_INV_WEAPON, p, 0, 0); break;
default : game->playSound(TR::SND_INV_SHOW, p, 0, 0); break;
}
item.choose();
}
}
}
float w = 90.0f * DEG2RAD * Core::deltaTime;
int itemIndex = getItemIndex(page, index);
for (int i = 0; i < itemsCount; i++) {
items[i].update();
float &angle = items[i].angle;
if (itemIndex != i || chosen) {
if (angle == 0.0f) {
continue;
} else if (angle < 0.0f) {
angle += w;
if (angle > 0.0f)
angle = 0.0f;
} else if (angle > 0.0f) {
angle -= w;
if (angle < 0.0f)
angle = 0.0f;
}
} else
angle += w;
angle = clampAngle(angle);
}
if (ready && chosen && phaseChoose == 1.0f && item.anim->isEnded) {
TR::Entity::Type type = item.type;
if (type == TR::Entity::INV_PISTOLS || type == TR::Entity::INV_SHOTGUN || type == TR::Entity::INV_MAGNUMS || type == TR::Entity::INV_UZIS ||
type == TR::Entity::INV_MEDIKIT_SMALL || type == TR::Entity::INV_MEDIKIT_BIG) {
game->invUse(type, TR::Entity::NONE);
toggle();
}
}
}
void prepareBackground() {
Core::setDepthTest(false);
// vertical blur
Core::setTarget(background[1], true);
game->setShader(Core::passFilter, Shader::FILTER_BLUR, false, false);
Core::active.shader->setParam(uParam, vec4(0, 1, 1.0f / INVENTORY_BG_SIZE, 0));;
background[0]->bind(sDiffuse);
game->getMesh()->renderQuad();
// horizontal blur
Core::setTarget(background[2], true);
game->setShader(Core::passFilter, Shader::FILTER_BLUR, false, false);
Core::active.shader->setParam(uParam, vec4(1, 0, 1.0f / INVENTORY_BG_SIZE, 0));;
background[1]->bind(sDiffuse);
game->getMesh()->renderQuad();
// grayscale
Core::setTarget(background[1], true);
game->setShader(Core::passFilter, Shader::FILTER_GRAYSCALE, false, false);
Core::active.shader->setParam(uParam, vec4(1, 0, 0, 0));;
background[2]->bind(sDiffuse);
game->getMesh()->renderQuad();
Core::setTarget(NULL, true);
Core::setDepthTest(true);
}
void renderItemText(const Item &item, float width) {
UI::textOut(game, vec2(0, 480 - 16), item.desc.name, UI::aCenter, width);
if (item.count > 1) {
char spec;
switch (item.type) {
case TR::Entity::INV_SHOTGUN : spec = 12; break;
case TR::Entity::INV_MAGNUMS : spec = 13; break;
case TR::Entity::INV_UZIS : spec = 14; break;
default : spec = 0;
}
char buf[16];
sprintf(buf, "%d %c", item.count, spec);
for (int i = 0; buf[i] != ' '; i++)
buf[i] -= 47;
UI::textOut(game, vec2(width / 2 - 160, 480 - 96), buf, UI::aRight, 320);
}
}
float getAngle(int index, int count) {
return PI * 2.0f / float(count) * index;
}
int getItemsCount(int page) {
int count = 0;
for (int i = 0; i < itemsCount; i++)
if (items[i].desc.page == page)
count++;
return count;
}
void renderPage(int page) {
float phase = page == targetPage ? phasePage : (1.0f - phasePage);
float alpha = 1.0f - phaseRing * phase;
alpha *= alpha;
alpha = 1.0f - alpha;
Core::active.shader->setParam(uMaterial, vec4(1.0f, 0.4f, 0.0f, alpha));
int count = getItemsCount(page);
vec2 cpos(1286, 256 + 1280 * (1.0f - phaseRing));
float ringTilt = cpos.angle();
float radius = phaseRing * INVENTORY_MAX_RADIUS * phase;
float collapseAngle = phaseRing * phase * PI - PI;
float ringHeight = lerp(float(this->page), float(targetPage), hermite(phasePage)) * INVENTORY_HEIGHT;
float angle = getAngle(pageItemIndex[page], count);
if (phaseSelect < 1.0f)
angle = lerpAngle(angle, getAngle(targetIndex, count), hermite(phaseSelect));
int itemIndex = 0;
for (int i = 0; i < itemsCount; i++) {
Item &item = items[i];
if (item.desc.page != page)
continue;
float a = getAngle(itemIndex, count) - angle - collapseAngle;
float ia = item.angle;
float ra = ringTilt;
float rd = radius;
if (itemIndex == pageItemIndex[page] && (chosen || phaseChoose > 0.0f)) {
ia *= 1.0f - phaseChoose;
ra *= 1.0f - phaseChoose;
rd += 296 * phaseChoose;
}
Basis basis = Basis(quat(vec3(1, 0, 0), ra), vec3(0.0f));
basis = basis * Basis(quat(vec3(0, 1, 0), PI + ia - a), vec3(sinf(a), 0, -cosf(a)) * rd - vec3(0, item.desc.page * INVENTORY_HEIGHT - ringHeight, 0));
item.render(game, basis);
itemIndex++;
}
}
void render() {
// background
Core::setDepthTest(false);
game->setShader(Core::passFilter, Shader::FILTER_MIXER, false, false);
Core::active.shader->setParam(uParam, vec4(phaseRing, 1.0f - phaseRing * 0.4f, 0, 0));;
background[0]->bind(sDiffuse); // orignal image
background[1]->bind(sNormal); // blured grayscale image
game->getMesh()->renderQuad();
Core::setDepthTest(true);
Core::setBlending(bmAlpha);
// items
game->setupBinding();
Core::mLightProj.identity();
Core::mView.identity();
Core::mView.translate(vec3(0, 0, -1286)); // y = -96 in title
Core::mView.up *= -1.0f;
Core::mView.dir *= -1.0f;
Core::mViewInv = Core::mView.inverse();
float aspect = float(Core::width) / float(Core::height);
Core::mProj = mat4(70.0f, aspect, 32.0f, 2048.0f);
Core::mViewProj = Core::mProj * Core::mView;
Core::viewPos = Core::mViewInv.getPos();
Core::whiteTex->bind(sShadow);
game->setShader(Core::passCompose, Shader::ENTITY, false, false);
vec3 ambient[6] = {
vec3(0.4f), vec3(0.2f), vec3(0.4f), vec3(0.5f), vec3(0.4f), vec3(0.6f)
};
Core::lightPos[0] = vec3(1000, 2000, 1000);
Core::lightColor[0] = vec4(1, 1, 1, 8192);
for (int i = 1; i < MAX_LIGHTS; i++)
Core::lightColor[1] = vec4(0, 0, 0, 1);
Core::active.shader->setParam(uLightPos, Core::lightPos[0], MAX_LIGHTS);
Core::active.shader->setParam(uLightColor, Core::lightColor[0], MAX_LIGHTS);
Core::active.shader->setParam(uAmbient, ambient[0], 6);
renderPage(page);
if (page != targetPage)
renderPage(targetPage);
if (phaseRing < 1.0f)
return;
Core::setDepthTest(false);
Core::setBlending(bmAlpha);
Core::setCulling(cfNone);
game->setupBinding();
float w = 480 * aspect;
Core::mViewProj = mat4(0.0f, w, 480, 0.0f, 0.0f, 1.0f);
game->setShader(Core::passGUI, Shader::DEFAULT);
Core::active.shader->setParam(uMaterial, vec4(1.0f));
Core::active.shader->setParam(uPosScale, vec4(0.0f, 0.0f, 1.0f, 1.0f));
UI::textBegin();
static const char* pageTitle[PAGE_MAX] = { "OPTION", "INVENTORY", "ITEMS" };
UI::textOut(game, vec2( 0, 32), pageTitle[page], UI::aCenter, w);
if (page < PAGE_ITEMS && getItemsCount(page + 1)) {
UI::textOut(game, vec2(16, 32), "\x5B", UI::aLeft, w);
UI::textOut(game, vec2( 0, 32), "\x5B", UI::aRight, w - 20);
}
if (page > PAGE_OPTION && getItemsCount(page - 1)) {
UI::textOut(game, vec2(16, 480 - 16), "\x5D", UI::aLeft, w);
UI::textOut(game, vec2(0, 480 - 16), "\x5D", UI::aRight, w - 20);
}
if (index == targetIndex)
renderItemText(items[getItemIndex(page, index)], w);
UI::textEnd(game);
Core::setCulling(cfFront);
Core::setBlending(bmNone);
Core::setDepthTest(true);
}
}; };
#endif #endif

View File

@@ -6,7 +6,6 @@
#include "character.h" #include "character.h"
#include "trigger.h" #include "trigger.h"
#include "sprite.h" #include "sprite.h"
#include "inventory.h"
#define TURN_FAST PI #define TURN_FAST PI
#define TURN_FAST_BACK PI * 3.0f / 4.0f #define TURN_FAST_BACK PI * 3.0f / 4.0f
@@ -21,6 +20,8 @@
#define LARA_TILT_SPEED (DEG2RAD * 37.5f) #define LARA_TILT_SPEED (DEG2RAD * 37.5f)
#define LARA_TILT_MAX (DEG2RAD * 10.0f) #define LARA_TILT_MAX (DEG2RAD * 10.0f)
#define LARA_MAX_HEALTH 1000
#define LARA_HANG_OFFSET 724 #define LARA_HANG_OFFSET 724
#define LARA_HEIGHT 762 #define LARA_HEIGHT 762
#define LARA_HEIGHT_WATER 400 #define LARA_HEIGHT_WATER 400
@@ -213,7 +214,6 @@ struct Lara : Character {
ActionCommand actionList[MAX_TRIGGER_ACTIONS]; ActionCommand actionList[MAX_TRIGGER_ACTIONS];
Inventory inventory;
int lastPickUp; int lastPickUp;
int viewTarget; int viewTarget;
int roomPrev; // water out from room int roomPrev; // water out from room
@@ -384,7 +384,7 @@ struct Lara : Character {
} *braid; } *braid;
Lara(IGame *game, int entity, bool home) : Character(game, entity, 1000), home(home), wpnCurrent(Weapon::EMPTY), wpnNext(Weapon::EMPTY), chestOffset(pos), viewTarget(-1), braid(NULL) { Lara(IGame *game, int entity, bool home) : Character(game, entity, LARA_MAX_HEALTH), home(home), wpnCurrent(Weapon::EMPTY), wpnNext(Weapon::EMPTY), chestOffset(pos), viewTarget(-1), braid(NULL) {
if (getEntity().type == TR::Entity::LARA) { if (getEntity().type == TR::Entity::LARA) {
if (getRoom().flags.water) if (getRoom().flags.water)
@@ -399,7 +399,11 @@ struct Lara : Character {
getEntity().flags.active = 1; getEntity().flags.active = 1;
initMeshOverrides(); initMeshOverrides();
memset(weapons, -1, sizeof(weapons)); weapons[Weapon::PISTOLS].ammo = -1;
weapons[Weapon::SHOTGUN].ammo = -1;
weapons[Weapon::MAGNUMS].ammo = -1;
weapons[Weapon::UZIS ].ammo = -1;
if (!home) { if (!home) {
weapons[Weapon::PISTOLS].ammo = 0; weapons[Weapon::PISTOLS].ammo = 0;
weapons[Weapon::SHOTGUN].ammo = 9000; weapons[Weapon::SHOTGUN].ammo = 9000;
@@ -666,7 +670,11 @@ struct Lara : Character {
} }
void wpnChange(Weapon::Type wType) { void wpnChange(Weapon::Type wType) {
if (wpnCurrent == wType || home) return; if (wpnCurrent == wType || home) {
if (emptyHands())
wpnDraw();
return;
}
wpnNext = wType; wpnNext = wType;
wpnHide(); wpnHide();
} }
@@ -803,11 +811,6 @@ struct Lara : Character {
updateTargets(); updateTargets();
updateOverrides(); updateOverrides();
if (Input::down[ik1]) wpnChange(Weapon::PISTOLS);
if (Input::down[ik2]) wpnChange(Weapon::SHOTGUN);
if (Input::down[ik3]) wpnChange(Weapon::MAGNUMS);
if (Input::down[ik4]) wpnChange(Weapon::UZIS);
if (wpnNext != Weapon::EMPTY && emptyHands()) { if (wpnNext != Weapon::EMPTY && emptyHands()) {
wpnSet(wpnNext); wpnSet(wpnNext);
wpnDraw(); wpnDraw();
@@ -1233,6 +1236,21 @@ struct Lara : Character {
Core::lightColor[1 + 0] = Core::lightColor[1 + 1] = vec4(0, 0, 0, 1); Core::lightColor[1 + 0] = Core::lightColor[1 + 1] = vec4(0, 0, 0, 1);
}; };
void useItem(TR::Entity::Type item) {
switch (item) {
case TR::Entity::INV_PISTOLS : wpnChange(Lara::Weapon::PISTOLS); break;
case TR::Entity::INV_SHOTGUN : wpnChange(Lara::Weapon::SHOTGUN); break;
case TR::Entity::INV_MAGNUMS : wpnChange(Lara::Weapon::MAGNUMS); break;
case TR::Entity::INV_UZIS : wpnChange(Lara::Weapon::UZIS); break;
case TR::Entity::INV_MEDIKIT_SMALL :
case TR::Entity::INV_MEDIKIT_BIG :
health = min(LARA_MAX_HEALTH, health + (item == TR::Entity::INV_MEDIKIT_SMALL ? LARA_MAX_HEALTH / 2 : LARA_MAX_HEALTH));
playSound(TR::SND_HEALTH, pos, Sound::PAN);
break;
default : ;
}
}
bool waterOut() { bool waterOut() {
// TODO: playSound 36 // TODO: playSound 36
if (collision.side != Collision::FRONT || pos.y - collision.info[Collision::FRONT].floor > 256 + 128) if (collision.side != Collision::FRONT || pos.y - collision.info[Collision::FRONT].floor > 256 + 128)
@@ -1298,22 +1316,6 @@ struct Lara : Character {
return false; return false;
} }
bool useItem(TR::Entity::Type item, TR::Entity::Type slot) {
if (item == TR::Entity::NONE) {
switch (slot) {
case TR::Entity::KEY_HOLE_1 : item = TR::Entity::KEY_1; break; // TODO: 1-4
case TR::Entity::PUZZLE_HOLE_1 : item = TR::Entity::PUZZLE_1; break;
default : return false;
}
}
if (inventory.getCount(item) > 0) {
inventory.remove(item);
return true;
}
return false;
}
void alignByItem(Controller *item, const TR::Limits::Limit &limit, bool dx, bool ay) { void alignByItem(Controller *item, const TR::Limits::Limit &limit, bool dx, bool ay) {
if (ay) if (ay)
angle = item->angle; angle = item->angle;
@@ -1390,7 +1392,7 @@ struct Lara : Character {
limit = actionState == STATE_USE_KEY ? &TR::Limits::KEY_HOLE : &TR::Limits::PUZZLE_HOLE; limit = actionState == STATE_USE_KEY ? &TR::Limits::KEY_HOLE : &TR::Limits::PUZZLE_HOLE;
if (!checkInteraction((Controller*)level->entities[info.trigCmd[0].args].controller, *limit, isPressed(ACTION))) if (!checkInteraction((Controller*)level->entities[info.trigCmd[0].args].controller, *limit, isPressed(ACTION)))
return; return;
if (!useItem(TR::Entity::NONE, level->entities[info.trigCmd[0].args].type)) { if (!game->invUse(TR::Entity::NONE, level->entities[info.trigCmd[0].args].type)) {
playSound(TR::SND_NO, pos, Sound::PAN); playSound(TR::SND_NO, pos, Sound::PAN);
return; return;
} }
@@ -1884,32 +1886,21 @@ struct Lara : Character {
input = Character::getInput(); input = Character::getInput();
if (input & DEATH) return input; if (input & DEATH) return input;
int &p = Input::joy.POV; if (Input::state[cUp]) input |= FORTH;
if (Input::state[cRight]) input |= RIGHT;
#ifndef LEVEL_EDITOR if (Input::state[cDown]) input |= BACK;
if (Input::down[ikW]) input |= FORTH; if (Input::state[cLeft]) input |= LEFT;
if (Input::down[ikD]) input |= RIGHT; if (Input::state[cRoll]) input = FORTH | BACK;
if (Input::down[ikS]) input |= BACK; if (Input::state[cStepRight]) input = WALK | RIGHT;
if (Input::down[ikA]) input |= LEFT; if (Input::state[cStepLeft]) input = WALK | LEFT;
#endif if (Input::state[cJump]) input |= JUMP;
if (Input::state[cWalk]) input |= WALK;
if (Input::down[ikUp] || p == 8 || p == 1 || p == 2) input |= FORTH; if (Input::state[cAction]) input |= ACTION;
if (Input::down[ikRight] || p == 2 || p == 3 || p == 4) input |= RIGHT; if (Input::state[cWeapon]) input |= WEAPON;
if (Input::down[ikDown] || p == 4 || p == 5 || p == 6) input |= BACK;
if (Input::down[ikLeft] || p == 6 || p == 7 || p == 8) input |= LEFT;
if (Input::down[ikJoyB]) input = FORTH | BACK; // roll
if (Input::down[ikJoyRT] || Input::down[ikX]) input = WALK | RIGHT; // step right
if (Input::down[ikJoyLT] || Input::down[ikZ]) input = WALK | LEFT; // step left
if (Input::down[ikSpace] || Input::down[ikJoyX]) input |= JUMP;
if (Input::down[ikShift] || Input::down[ikJoyLB]) input |= WALK;
if (Input::down[ikE] || Input::down[ikCtrl] || Input::down[ikJoyA]) input |= ACTION;
if (Input::down[ikQ] || Input::down[ikAlt] || Input::down[ikJoyY]) input |= WEAPON;
// analog control // analog control
rotFactor = vec2(1.0f); rotFactor = vec2(1.0f);
if (Input::down[ikJoyL]) input = FORTH | BACK;
if ((state == STATE_STOP || state == STATE_SURF_TREAD || state == STATE_HANG) && fabsf(Input::joy.L.x) < 0.5f && fabsf(Input::joy.L.y) < 0.5f) if ((state == STATE_STOP || state == STATE_SURF_TREAD || state == STATE_HANG) && fabsf(Input::joy.L.x) < 0.5f && fabsf(Input::joy.L.y) < 0.5f)
return input; return input;
@@ -1945,7 +1936,7 @@ struct Lara : Character {
int pickupFrame = stand == STAND_GROUND ? PICKUP_FRAME_GROUND : PICKUP_FRAME_UNDERWATER; int pickupFrame = stand == STAND_GROUND ? PICKUP_FRAME_GROUND : PICKUP_FRAME_UNDERWATER;
if (animation.isFrameActive(pickupFrame)) { if (animation.isFrameActive(pickupFrame)) {
item.flags.invisible = true; item.flags.invisible = true;
inventory.add(item.type, 1); game->invAdd(item.type, 1);
} }
} }
break; break;

View File

@@ -9,6 +9,7 @@
#include "enemy.h" #include "enemy.h"
#include "camera.h" #include "camera.h"
#include "trigger.h" #include "trigger.h"
#include "inventory.h"
#ifdef _DEBUG #ifdef _DEBUG
#include "debug.h" #include "debug.h"
@@ -16,6 +17,7 @@
struct Level : IGame { struct Level : IGame {
TR::Level level; TR::Level level;
Inventory inventory;
Texture *atlas; Texture *atlas;
Texture *cube; Texture *cube;
MeshBuilder *mesh; MeshBuilder *mesh;
@@ -36,8 +38,11 @@ struct Level : IGame {
WaterCache *waterCache; WaterCache *waterCache;
ZoneCache *zoneCache; ZoneCache *zoneCache;
Sound::Sample *sndAmbient; Sound::Sample *sndSoundtrack;
Sound::Sample *sndUnderwater; Sound::Sample *sndUnderwater;
Sound::Sample *sndCurrent;
bool lastTitle;
// IGame implementation ======== // IGame implementation ========
virtual TR::Level* getLevel() { virtual TR::Level* getLevel() {
@@ -83,6 +88,16 @@ struct Level : IGame {
shaderCache->bind(pass, type, (underwater ? ShaderCache::FX_UNDERWATER : 0) | (alphaTest ? ShaderCache::FX_ALPHA_TEST : 0) | ((params->clipHeight != NO_CLIP_PLANE && pass == Core::passCompose) ? ShaderCache::FX_CLIP_PLANE : 0)); shaderCache->bind(pass, type, (underwater ? ShaderCache::FX_UNDERWATER : 0) | (alphaTest ? ShaderCache::FX_ALPHA_TEST : 0) | ((params->clipHeight != NO_CLIP_PLANE && pass == Core::passCompose) ? ShaderCache::FX_CLIP_PLANE : 0));
} }
virtual void setupBinding() {
atlas->bind(sDiffuse);
Core::whiteTex->bind(sNormal);
Core::whiteTex->bind(sMask);
Core::whiteTex->bind(sReflect);
cube->bind(sEnvironment);
Core::basis.identity();
}
virtual void renderEnvironment(int roomIndex, const vec3 &pos, Texture **targets, int stride = 0) { virtual void renderEnvironment(int roomIndex, const vec3 &pos, Texture **targets, int stride = 0) {
PROFILE_MARKER("ENVIRONMENT"); PROFILE_MARKER("ENVIRONMENT");
Core::eye = 0.0f; Core::eye = 0.0f;
@@ -111,9 +126,40 @@ struct Level : IGame {
virtual void fxQuake(float time) { virtual void fxQuake(float time) {
camera->shake = time; camera->shake = time;
} }
virtual bool invUse(TR::Entity::Type item, TR::Entity::Type slot) {
lara->useItem(item);
return inventory.use(item, slot);
}
virtual void invAdd(TR::Entity::Type type, int count) {
inventory.add(type, count);
}
virtual Sound::Sample* playSound(int id, const vec3 &pos, int flags, int group = -1) const {
if (level.version == TR::Level::VER_TR1_PSX && id == TR::SND_SECRET)
return NULL;
int16 a = level.soundsMap[id];
if (a == -1) return NULL;
TR::SoundInfo &b = level.soundsInfo[a];
if (b.chance == 0 || (rand() & 0x7fff) <= b.chance) {
int index = b.offset + rand() % b.flags.count;
float volume = (float)b.volume / 0x7FFF;
float pitch = b.flags.pitch ? (0.9f + randf() * 0.2f) : 1.0f;
if (b.flags.mode == 1) flags |= Sound::UNIQUE;
//if (b.flags.mode == 2) flags |= Sound::REPLAY;
if (b.flags.mode == 3) flags |= Sound::SYNC;
if (b.flags.gain) volume = max(0.0f, volume - randf() * 0.25f);
if (b.flags.fixed) flags |= Sound::LOOP;
return Sound::play(level.getSampleStream(index), pos, volume, pitch, flags, group * 1000 + index);
}
return NULL;
}
//============================== //==============================
Level(Stream &stream, Stream *snd, bool demo, bool home) : level(stream, demo), lara(NULL) { Level(Stream &stream, Stream *snd, bool demo, bool home) : level(stream, demo), inventory(this), lara(NULL) {
params->time = 0.0f; params->time = 0.0f;
#ifdef _DEBUG #ifdef _DEBUG
@@ -241,6 +287,9 @@ struct Level : IGame {
} }
} }
lastTitle = false;
if (!isTitle()) {
ASSERT(lara != NULL); ASSERT(lara != NULL);
camera = new Camera(this, lara); camera = new Camera(this, lara);
@@ -255,16 +304,28 @@ struct Level : IGame {
// init sounds // init sounds
//Sound::play(Sound::openWAD("05_Lara's_Themes.wav"), 1, 1, 0); //Sound::play(Sound::openWAD("05_Lara's_Themes.wav"), 1, 1, 0);
sndAmbient = Sound::play(snd, vec3(0.0f), 1, 1, Sound::Flags::LOOP); sndSoundtrack = Sound::play(snd, vec3(0.0f), 1, 1, Sound::Flags::LOOP);
sndUnderwater = lara->playSound(TR::SND_UNDERWATER, vec3(0.0f), Sound::LOOP); sndUnderwater = lara->playSound(TR::SND_UNDERWATER, vec3(0.0f), Sound::LOOP);
if (sndUnderwater) if (sndUnderwater)
sndUnderwater->volume = 0.0f; sndUnderwater->volume = 0.0f;
sndCurrent = sndSoundtrack;
for (int i = 0; i < level.soundSourcesCount; i++) { for (int i = 0; i < level.soundSourcesCount; i++) {
TR::SoundSource &src = level.soundSources[i]; TR::SoundSource &src = level.soundSources[i];
lara->playSound(src.id, vec3(float(src.x), float(src.y), float(src.z)), Sound::PAN | Sound::LOOP | Sound::STATIC); lara->playSound(src.id, vec3(float(src.x), float(src.y), float(src.z)), Sound::PAN | Sound::LOOP | Sound::STATIC);
} }
} else {
camera = NULL;
ambientCache = NULL;
waterCache = NULL;
zoneCache = NULL;
shadow = NULL;
sndSoundtrack = NULL;
sndUnderwater = NULL;
sndCurrent = NULL;
}
} }
virtual ~Level() { virtual ~Level() {
@@ -289,6 +350,10 @@ struct Level : IGame {
Sound::stopAll(); Sound::stopAll();
} }
bool isTitle() {
return lara == NULL || inventory.isActive();
}
void initTextures() { void initTextures() {
if (!level.tilesCount) { if (!level.tilesCount) {
atlas = NULL; atlas = NULL;
@@ -554,6 +619,13 @@ struct Level : IGame {
lara->reset(TR::NO_ROOM, camera->pos, camera->angle.y, false); lara->reset(TR::NO_ROOM, camera->pos, camera->angle.y, false);
} }
#endif #endif
if (isTitle()) {
sndCurrent->volume = 0.0f;
Sound::reverb.setRoomSize(vec3(1.0f));
inventory.update();
return;
}
params->time += Core::deltaTime; params->time += Core::deltaTime;
for (int i = 0; i < level.entitiesCount; i++) { for (int i = 0; i < level.entitiesCount; i++) {
@@ -572,9 +644,14 @@ struct Level : IGame {
} }
camera->update(); camera->update();
float ambientVolume = camera->isUnderwater() ? 0.0f : 1.0f;
if (sndAmbient) sndAmbient->volume = ambientVolume; sndCurrent = camera->isUnderwater() ? sndUnderwater : sndSoundtrack;
if (sndUnderwater) sndUnderwater->volume = 1.0f - ambientVolume;
if (sndCurrent) {
if (sndSoundtrack && sndCurrent != sndSoundtrack) sndSoundtrack->volume = 0.0f;
if (sndUnderwater && sndCurrent != sndUnderwater) sndUnderwater->volume = 0.0f;
sndCurrent->volume = 1.0f;
}
if (waterCache) if (waterCache)
waterCache->update(); waterCache->update();
@@ -585,16 +662,7 @@ struct Level : IGame {
camera->setup(Core::pass == Core::passCompose); camera->setup(Core::pass == Core::passCompose);
atlas->bind(sDiffuse); setupBinding();
Core::whiteTex->bind(sNormal);
Core::whiteTex->bind(sMask);
Core::whiteTex->bind(sReflect);
cube->bind(sEnvironment);
if (!Core::support.VAO)
mesh->bind();
//Core::mViewProj = Core::mLightProj;
Core::basis.identity();
// clear visibility flag for rooms // clear visibility flag for rooms
for (int i = 0; i < level.roomsCount; i++) for (int i = 0; i < level.roomsCount; i++)
@@ -694,39 +762,8 @@ struct Level : IGame {
Core::setClearColor(vec4(0.0f, 0.0f, 0.0f, 0.0f)); Core::setClearColor(vec4(0.0f, 0.0f, 0.0f, 0.0f));
} }
void render() {
Core::invalidateTarget(true, true);
params->clipHeight = NO_CLIP_PLANE;
params->clipSign = 1.0f;
params->waterHeight = params->clipHeight;
if (ambientCache)
ambientCache->precessQueue();
if (waterCache)
waterCache->reset();
if (shadow)
renderShadows(lara->getRoomIndex());
Core::setClearStencil(128);
Core::setTarget(NULL, true);
Core::setViewport(0, 0, Core::width, Core::height);
if (waterCache)
waterCache->checkVisibility = true;
renderCompose(camera->getRoomIndex(), true);
if (waterCache) {
waterCache->checkVisibility = false;
if (waterCache->visible) {
waterCache->renderMask();
waterCache->getRefract();
waterCache->simulate();
waterCache->render();
}
}
#ifdef _DEBUG #ifdef _DEBUG
void renderDebug() {
// Core::mViewInv = camera->mViewInv; // Core::mViewInv = camera->mViewInv;
// Core::mView = Core::mViewInv.inverse(); // Core::mView = Core::mViewInv.inverse();
Core::setViewport(0, 0, Core::width, Core::height); Core::setViewport(0, 0, Core::width, Core::height);
@@ -868,107 +905,74 @@ struct Level : IGame {
Core::setBlending(bmNone); Core::setBlending(bmNone);
/*
static int dbg_ambient = 0;
dbg_ambient = int(params->time * 2) % 4;
shadow->unbind(sShadow);
atlas->bind(sDiffuse);
glEnable(GL_TEXTURE_2D);
glDisable(GL_CULL_FACE);
glColor3f(1, 1, 1);
for (int j = 0; j < 6; j++) {
glPushMatrix();
vec3 p = lara->pos;//getPos();
glTranslatef(p.x, p.y - 1024, p.z);
switch (j) {
case 0 : glRotatef( 90, 0, 1, 0); break;
case 1 : glRotatef(-90, 0, 1, 0); break;
case 2 : glRotatef(-90, 1, 0, 0); break;
case 3 : glRotatef( 90, 1, 0, 0); break;
case 4 : glRotatef( 0, 0, 1, 0); break;
case 5 : glRotatef(180, 0, 1, 0); break;
}
glTranslatef(0, 0, 128);
ambientCache->textures[j * 4 + dbg_ambient]->bind(sDiffuse);
glBegin(GL_QUADS);
glTexCoord2f(0, 0); glVertex3f(-128, 128, 0);
glTexCoord2f(1, 0); glVertex3f( 128, 128, 0);
glTexCoord2f(1, 1); glVertex3f( 128, -128, 0);
glTexCoord2f(0, 1); glVertex3f(-128, -128, 0);
glEnd();
glPopMatrix();
}
glEnable(GL_CULL_FACE);
glDisable(GL_TEXTURE_2D);
glLineWidth(4);
glBegin(GL_LINES);
float S = 64.0f;
for (int i = 0; i < level.roomsCount; i++) {
TR::Room &r = level.rooms[i];
for (int j = 0; j < r.xSectors * r.zSectors; j++) {
TR::Room::Sector &s = r.sectors[j];
vec3 p = vec3(float((j / r.zSectors) * 1024 + 512 + r.info.x),
float(max((s.floor - 2) * 256, (s.floor + s.ceiling) * 256 / 2)),
float((j % r.zSectors) * 1024 + 512 + r.info.z));
AmbientCache::Cube &cube = ambientCache->items[ambientCache->offsets[i] + j];
if (cube.status == AmbientCache::Cube::READY) {
glColor3f(powf(cube.colors[0].x, 1.0f / 2.2f), powf(cube.colors[0].y, 1.0f / 2.2f), powf(cube.colors[0].z, 1.0f / 2.2f));
glVertex3f(p.x + 0, p.y + 0, p.z + 0);
glVertex3f(p.x + S, p.y + 0, p.z + 0);
glColor3f(powf(cube.colors[1].x, 1.0f / 2.2f), powf(cube.colors[1].y, 1.0f / 2.2f), powf(cube.colors[1].z, 1.0f / 2.2f));
glVertex3f(p.x + 0, p.y + 0, p.z + 0);
glVertex3f(p.x - S, p.y + 0, p.z + 0);
glColor3f(powf(cube.colors[2].x, 1.0f / 2.2f), powf(cube.colors[2].y, 1.0f / 2.2f), powf(cube.colors[2].z, 1.0f / 2.2f));
glVertex3f(p.x + 0, p.y + 0, p.z + 0);
glVertex3f(p.x + 0, p.y + S, p.z + 0);
glColor3f(powf(cube.colors[3].x, 1.0f / 2.2f), powf(cube.colors[3].y, 1.0f / 2.2f), powf(cube.colors[3].z, 1.0f / 2.2f));
glVertex3f(p.x + 0, p.y + 0, p.z + 0);
glVertex3f(p.x + 0, p.y - S, p.z + 0);
glColor3f(powf(cube.colors[4].x, 1.0f / 2.2f), powf(cube.colors[4].y, 1.0f / 2.2f), powf(cube.colors[4].z, 1.0f / 2.2f));
glVertex3f(p.x + 0, p.y + 0, p.z + 0);
glVertex3f(p.x + 0, p.y + 0, p.z + S);
glColor3f(powf(cube.colors[5].x, 1.0f / 2.2f), powf(cube.colors[5].y, 1.0f / 2.2f), powf(cube.colors[5].z, 1.0f / 2.2f));
glVertex3f(p.x + 0, p.y + 0, p.z + 0);
glVertex3f(p.x + 0, p.y + 0, p.z - S);
}
}
}
glEnd();
glLineWidth(1);
*/
/*
shaders[shGUI]->bind();
Core::mViewProj = mat4(0, (float)Core::width, (float)Core::height, 0, 0, 1);
Core::active.shader->setParam(uViewProj, Core::mViewProj);
atlas->bind(0);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
*/
Debug::Level::info(level, lara->getEntity(), lara->animation); Debug::Level::info(level, lara->getEntity(), lara->animation);
Debug::end(); Debug::end();
#endif
} }
#endif
void renderGame() {
Core::invalidateTarget(true, true);
params->clipHeight = NO_CLIP_PLANE;
params->clipSign = 1.0f;
params->waterHeight = params->clipHeight;
if (ambientCache)
ambientCache->precessQueue();
if (waterCache)
waterCache->reset();
if (shadow)
renderShadows(lara->getRoomIndex());
Core::setClearStencil(128);
Core::setTarget(NULL, true);
if (waterCache)
waterCache->checkVisibility = true;
renderCompose(camera->getRoomIndex(), true);
if (waterCache) {
waterCache->checkVisibility = false;
if (waterCache->visible) {
waterCache->renderMask();
waterCache->getRefract();
waterCache->simulate();
waterCache->render();
}
}
}
void renderInventory() {
Core::setTarget(NULL, true);
inventory.render();
}
void render() {
bool title = isTitle();
bool copyBg = title && lastTitle != title;
if (copyBg) {
vec4 vp = Core::viewportDef;
Core::defaultTarget = inventory.background[0];
Core::setTarget(Core::defaultTarget, true);
renderGame();
Core::viewportDef = vp;
Core::defaultTarget = NULL;
inventory.prepareBackground();
}
if (!title)
renderGame();
if (title)
renderInventory();
lastTitle = title;
}
}; };
#endif #endif

View File

@@ -21,8 +21,13 @@ struct MeshRange {
MeshRange() : aIndex(-1) {} MeshRange() : aIndex(-1) {}
void setup() const { void setup(Vertex *offset) const {
Vertex *v = (Vertex*)(vStart * sizeof(Vertex)); glEnableVertexAttribArray(aCoord);
glEnableVertexAttribArray(aTexCoord);
glEnableVertexAttribArray(aNormal);
glEnableVertexAttribArray(aColor);
Vertex *v = (Vertex*)(offset + vStart);
glVertexAttribPointer(aCoord, 4, GL_SHORT, false, sizeof(Vertex), &v->coord); glVertexAttribPointer(aCoord, 4, GL_SHORT, false, sizeof(Vertex), &v->coord);
glVertexAttribPointer(aTexCoord, 4, GL_SHORT, false, sizeof(Vertex), &v->texCoord); glVertexAttribPointer(aTexCoord, 4, GL_SHORT, false, sizeof(Vertex), &v->texCoord);
glVertexAttribPointer(aNormal, 4, GL_SHORT, true, sizeof(Vertex), &v->normal); glVertexAttribPointer(aNormal, 4, GL_SHORT, true, sizeof(Vertex), &v->normal);
@@ -30,12 +35,9 @@ struct MeshRange {
} }
void bind(GLuint *VAO) const { void bind(GLuint *VAO) const {
if (aIndex > -1) { GLuint vao = aIndex == -1 ? 0 : VAO[aIndex];
if (Core::active.VAO != VAO[aIndex]) { if (Core::support.VAO && Core::active.VAO != vao)
glBindVertexArray(Core::active.VAO = VAO[aIndex]); glBindVertexArray(Core::active.VAO = vao);
}
} else
setup();
} }
}; };
@@ -52,7 +54,7 @@ struct Mesh {
Mesh(Index *indices, int iCount, Vertex *vertices, int vCount, int aCount) : VAO(NULL), iCount(iCount), vCount(vCount), aCount(aCount), aIndex(0) { Mesh(Index *indices, int iCount, Vertex *vertices, int vCount, int aCount) : VAO(NULL), iCount(iCount), vCount(vCount), aCount(aCount), aIndex(0) {
glGenBuffers(2, ID); glGenBuffers(2, ID);
bind(); bind(true);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, iCount * sizeof(Index), indices, GL_STATIC_DRAW); glBufferData(GL_ELEMENT_ARRAY_BUFFER, iCount * sizeof(Index), indices, GL_STATIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, vCount * sizeof(Vertex), vertices, GL_STATIC_DRAW); glBufferData(GL_ARRAY_BUFFER, vCount * sizeof(Vertex), vertices, GL_STATIC_DRAW);
@@ -74,40 +76,50 @@ struct Mesh {
if (Core::support.VAO) { if (Core::support.VAO) {
range.aIndex = aIndex++; range.aIndex = aIndex++;
range.bind(VAO); range.bind(VAO);
bind(); bind(true);
range.setup(); range.setup(NULL);
} else } else
range.aIndex = -1; range.aIndex = -1;
} }
void bind() { void bind(bool force = false) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ID[0]); if (force || Core::active.iBuffer != ID[0])
glBindBuffer(GL_ARRAY_BUFFER, ID[1]); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, Core::active.iBuffer = ID[0]);
if (force || Core::active.vBuffer != ID[1])
glEnableVertexAttribArray(aCoord); glBindBuffer(GL_ARRAY_BUFFER, Core::active.vBuffer = ID[1]);
glEnableVertexAttribArray(aTexCoord);
glEnableVertexAttribArray(aNormal);
glEnableVertexAttribArray(aColor);
} }
void DIP(const MeshRange &range) { void unbind() {
glDrawElements(GL_TRIANGLES, range.iCount, GL_UNSIGNED_SHORT, (GLvoid*)(range.iStart * sizeof(Index))); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, Core::active.iBuffer = 0);
glBindBuffer(GL_ARRAY_BUFFER, Core::active.vBuffer = 0);
}
void DIP(const MeshRange &range, Index *iOffset = 0) {
glDrawElements(GL_TRIANGLES, range.iCount, GL_UNSIGNED_SHORT, (GLvoid*)(iOffset + range.iStart) );
Core::stats.dips++; Core::stats.dips++;
Core::stats.tris += range.iCount / 3; Core::stats.tris += range.iCount / 3;
} }
void render(const MeshRange &range) { void render(const MeshRange &range, Index *iOffset = NULL, Vertex *vOffset = NULL) {
range.bind(VAO); range.bind(VAO);
if (vOffset) {
unbind();
range.setup(vOffset);
} else if (range.aIndex == -1) {
bind();
range.setup(NULL);
};
if (Core::active.stencilTwoSide && Core::support.stencil == 0) { if (Core::active.stencilTwoSide && Core::support.stencil == 0) {
Core::setCulling(cfBack); Core::setCulling(cfBack);
glStencilOp(GL_KEEP, GL_DECR, GL_KEEP); glStencilOp(GL_KEEP, GL_DECR, GL_KEEP);
DIP(range); DIP(range, iOffset);
Core::setCulling(cfFront); Core::setCulling(cfFront);
glStencilOp(GL_KEEP, GL_INCR, GL_KEEP); glStencilOp(GL_KEEP, GL_INCR, GL_KEEP);
} }
DIP(range); DIP(range, iOffset);
} }
}; };
@@ -904,22 +916,39 @@ struct MeshBuilder {
if (tex) addTexCoord(vertices, vCount, tex, false); if (tex) addTexCoord(vertices, vCount, tex, false);
} }
void addSprite(Index *indices, Vertex *vertices, int &iCount, int &vCount, int vStart, int16 x, int16 y, int16 z, const TR::SpriteTexture &sprite, uint8 intensity) { void addSprite(Index *indices, Vertex *vertices, int &iCount, int &vCount, int vStart, int16 x, int16 y, int16 z, const TR::SpriteTexture &sprite, uint8 intensity, bool expand = false) {
addQuad(indices, iCount, vCount, vStart, NULL, NULL); addQuad(indices, iCount, vCount, vStart, NULL, NULL);
Vertex *quad = &vertices[vCount]; Vertex *quad = &vertices[vCount];
quad[0].coord = quad[1].coord = quad[2].coord = quad[3].coord = { x, y, z, 0 }; int16 x0, y0, x1, y1;
if (expand) {
x0 = x + int16(sprite.l);
y0 = y + int16(sprite.t);
x1 = x + int16(sprite.r);
y1 = y + int16(sprite.b);
} else {
x0 = x1 = x;
y0 = y1 = y;
}
quad[0].coord = { x0, y0, z, 0 };
quad[1].coord = { x1, y0, z, 0 };
quad[2].coord = { x1, y1, z, 0 };
quad[3].coord = { x0, y1, z, 0 };
quad[0].normal = quad[1].normal = quad[2].normal = quad[3].normal = { 0, 0, 0, 0 }; quad[0].normal = quad[1].normal = quad[2].normal = quad[3].normal = { 0, 0, 0, 0 };
quad[0].color = quad[1].color = quad[2].color = quad[3].color = { 255, 255, 255, intensity }; quad[0].color = quad[1].color = quad[2].color = quad[3].color = { 255, 255, 255, intensity };
int tx = (sprite.tile % 4) * 256; int tx = (sprite.tile % 4) * 256;
int ty = (sprite.tile / 4) * 256; int ty = (sprite.tile / 4) * 256;
int16 u0 = ((tx + sprite.texCoord[0].x + 1) << 5); int16 u0 = (((tx + sprite.texCoord[0].x) << 5));
int16 v0 = ((ty + sprite.texCoord[0].y + 1) << 5); int16 v0 = (((ty + sprite.texCoord[0].y) << 5));
int16 u1 = ((tx + sprite.texCoord[1].x - 1) << 5); int16 u1 = (((tx + sprite.texCoord[1].x) << 5));
int16 v1 = ((ty + sprite.texCoord[1].y - 1) << 5); int16 v1 = (((ty + sprite.texCoord[1].y) << 5));
quad[0].texCoord = { u0, v0, sprite.l, sprite.t }; quad[0].texCoord = { u0, v0, sprite.l, sprite.t };
quad[1].texCoord = { u1, v0, sprite.r, sprite.t }; quad[1].texCoord = { u1, v0, sprite.r, sprite.t };
@@ -933,6 +962,15 @@ struct MeshBuilder {
mesh->bind(); mesh->bind();
} }
void renderBuffer(Index *indices, Vertex *vertices, int iCount) {
MeshRange range;
range.iStart = 0;
range.vStart = 0;
range.iCount = iCount;
mesh->render(range, indices, vertices);
}
void renderRoomGeometry(int roomIndex, bool transparent) { void renderRoomGeometry(int roomIndex, bool transparent) {
ASSERT(rooms[roomIndex].geometry[transparent].iCount > 0); ASSERT(rooms[roomIndex].geometry[transparent].iCount > 0);
mesh->render(rooms[roomIndex].geometry[transparent]); mesh->render(rooms[roomIndex].geometry[transparent]);
@@ -1000,57 +1038,6 @@ struct MeshBuilder {
d[0] = clGrayD; d[1] = clGrayL; d[2] = clGrayL; d[3] = clGrayL; d[4] = clGrayL; d+= 1024; d[0] = clGrayD; d[1] = clGrayL; d[2] = clGrayL; d[3] = clGrayL; d[4] = clGrayL; d+= 1024;
*/ */
} }
void textOut(const vec2 &pos, const vec4 &color, char *text) {
const static uint8 char_width[110] = {
14, 11, 11, 11, 11, 11, 11, 13, 8, 11, 12, 11, 13, 13, 12, 11, 12, 12, 11, 12, 13, 13, 13, 12,
12, 11, 9, 9, 9, 9, 9, 9, 9, 9, 5, 9, 9, 5, 12, 10, 9, 9, 9, 8, 9, 8, 9, 9, 11, 9, 9, 9, 12, 8,
10, 10, 10, 10, 10, 9, 10, 10, 5, 5, 5, 11, 9, 10, 8, 6, 6, 7, 7, 3, 11, 8, 13, 16, 9, 4, 12, 12,
7, 5, 7, 7, 7, 7, 7, 7, 7, 7, 16, 14, 14, 14, 16, 16, 16, 16, 16, 12, 14, 8, 8, 8, 8, 8, 8, 8 };
static const uint8 char_map[102] = {
0, 64, 66, 78, 77, 74, 78, 79, 69, 70, 92, 72, 63, 71, 62, 68, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, 73, 73, 66, 74, 75, 65, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25, 80, 76, 81, 97, 98, 77, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 100, 101, 102, 67, 0, 0, 0, 0, 0, 0, 0 };
if (!text) return;
Core::active.shader->setParam(uMaterial, vec4(1.0, 0.0, 0.0, 1.0));
//text = "a: b";
Basis basis;
basis.identity();
basis.translate(vec3(pos.x, pos.y, 0.0f));
// text = "A";
while (char c = *text++) {
int frame = c > 10 ? (c > 15 ? char_map[c - 32] : c + 91) : c + 81;
//int frame = T_remapASCII[c - 32];
/*
if (c >= 'A' && c <= 'Z')
frame = c - 'A';
else if (c >= 'a' && c <= 'z')
frame = 26 + c - 'a';
else if (c >= '0' && c <= '9')
frame = 52 + c - '0';
else {
if (c == ' ')
m.translate(vec3(16, 0.0f, 0.0f));
continue;
}
*/
if (c == ' ' || c == '_') {
basis.translate(vec3(char_width[0], 0.0f, 0.0f));
continue;
}
Core::active.shader->setParam(uBasis, basis);
renderSprite(15, frame);
basis.translate(vec3(char_width[frame], 0.0f, 0.0f));
}
}
}; };
#endif #endif

View File

@@ -98,7 +98,7 @@ int getPOV(int x, int y) {
InputKey keyToInputKey(int code) { InputKey keyToInputKey(int code) {
int codes[] = { int codes[] = {
21, 22, 19, 20, 62, 66, 111, 59, 113, 57, 21, 22, 19, 20, 62, 61, 66, 111, 59, 113, 57,
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,

View File

@@ -78,7 +78,7 @@ int getTime() {
InputKey keyToInputKey(int code) { InputKey keyToInputKey(int code) {
int codes[] = { int codes[] = {
113, 114, 111, 116, 65, 36, 9, 50, 37, 64, 113, 114, 111, 116, 65, 23, 36, 9, 50, 37, 64,
19, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 10, 11, 12, 13, 14, 15, 16, 17, 18,
38, 56, 54, 40, 26, 41, 42, 43, 31, 44, 45, 46, 58, 38, 56, 54, 40, 26, 41, 42, 43, 31, 44, 45, 46, 58,
57, 32, 33, 24, 27, 39, 28, 30, 55, 25, 53, 29, 52, 57, 32, 33, 24, 27, 39, 28, 30, 55, 25, 53, 29, 52,

View File

@@ -42,7 +42,7 @@ void soundInit() {
// common input functions // common input functions
InputKey keyToInputKey(int code) { InputKey keyToInputKey(int code) {
static const int codes[] = { static const int codes[] = {
0x7B, 0x7C, 0x7E, 0x7D, 0x31, 0x24, 0x35, 0x38, 0x3B, 0x3A, 0x7B, 0x7C, 0x7E, 0x7D, 0x31, 0x30, 0x24, 0x35, 0x38, 0x3B, 0x3A,
0x1D, 0x12, 0x13, 0x14, 0x15, 0x17, 0x16, 0x1A, 0x1C, 0x19, // 0..9 0x1D, 0x12, 0x13, 0x14, 0x15, 0x17, 0x16, 0x1A, 0x1C, 0x19, // 0..9
0x00, 0x0B, 0x08, 0x02, 0x0E, 0x03, 0x05, 0x04, 0x22, 0x26, 0x28, 0x25, 0x2E, // A..M 0x00, 0x0B, 0x08, 0x02, 0x0E, 0x03, 0x05, 0x04, 0x22, 0x26, 0x28, 0x25, 0x2E, // A..M
0x2D, 0x1F, 0x23, 0x0C, 0x0F, 0x01, 0x11, 0x20, 0x09, 0x0D, 0x07, 0x10, 0x06, // N..Z 0x2D, 0x1F, 0x23, 0x0C, 0x0F, 0x01, 0x11, 0x20, 0x09, 0x0D, 0x07, 0x10, 0x06, // N..Z

View File

@@ -142,14 +142,14 @@
<!-- <label for="browseFile">Browse Level</label> --> <!-- <label for="browseFile">Browse Level</label> -->
<input type="button" value="Browse Level" onclick="document.getElementById('browseFile').click();" /> (.PHD, .PSX) <input type="button" value="Browse Level" onclick="document.getElementById('browseFile').click();" /> (.PHD, .PSX)
<input type="checkbox" id="isHome"><label>alternative model (home suit, gold etc.)</label> <input type="checkbox" id="isHome"><label>alternative model (home suit, gold etc.)</label>
<br><br> <p style="margin:8px">
OpenLara on <a target="_blank" href="https://github.com/XProger/OpenLara">github</a> & <a target="_blank" href="https://www.facebook.com/OpenLaraTR">facebook</a><br> OpenLara on <a target="_blank" href="https://github.com/XProger/OpenLara">github</a> & <a target="_blank" href="https://www.facebook.com/OpenLaraTR">facebook</a><br><br>
controls:<br> controls:<br>
keyboad: move - WASD / arrows, jump - Space, action - E/Ctrl, draw weapon - Q, change weapon - 1-4, walk - Shift, side steps - ZX/walk+direction, camera - MouseR)<br> keyboad: move - arrow keys, jump - Alt, action - Ctrl, draw weapon - Space, inventory - Tab, walk - Shift, side steps - ZX, change view - V)<br>
gamepad: PSX controls for Xbox controller<br> gamepad: PSX controls for Xbox controller<br>
Change view: V<br>
Time Control: R - slow motion, T - fast motion<br> Time Control: R - slow motion, T - fast motion<br>
FullScreen: Alt + Enter FullScreen: Alt + Enter
</p>
</span> </span>
<script>(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script','//www.google-analytics.com/analytics.js','ga');ga('create', 'UA-60009035-1', 'auto');ga('send', 'pageview');</script> <script>(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script','//www.google-analytics.com/analytics.js','ga');ga('create', 'UA-60009035-1', 'auto');ga('send', 'pageview');</script>

View File

@@ -178,7 +178,7 @@ void changeWindowMode() {
InputKey keyToInputKey(int code) { InputKey keyToInputKey(int code) {
static const int codes[] = { static const int codes[] = {
0x25, 0x27, 0x26, 0x28, 0x20, 0x0D, 0x1B, 0x10, 0x11, 0x12, 0x25, 0x27, 0x26, 0x28, 0x20, 0x09, 0x0D, 0x1B, 0x10, 0x11, 0x12,
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',

View File

@@ -1,9 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<ClCompile Include="..\..\libs\minimp3\minimp3.cpp" />
<ClCompile Include="..\..\libs\stb_vorbis\stb_vorbis.c" />
<ClCompile Include="main.cpp" /> <ClCompile Include="main.cpp" />
<ClCompile Include="..\..\libs\minimp3\minimp3.cpp">
<Filter>libs\minimp3</Filter>
</ClCompile>
<ClCompile Include="..\..\libs\stb_vorbis\stb_vorbis.c">
<Filter>libs\stb_vorbis</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="..\..\animation.h" /> <ClInclude Include="..\..\animation.h" />
@@ -22,8 +26,6 @@
<ClInclude Include="..\..\inventory.h" /> <ClInclude Include="..\..\inventory.h" />
<ClInclude Include="..\..\lara.h" /> <ClInclude Include="..\..\lara.h" />
<ClInclude Include="..\..\level.h" /> <ClInclude Include="..\..\level.h" />
<ClInclude Include="..\..\libs\minimp3\libc.h" />
<ClInclude Include="..\..\libs\minimp3\minimp3.h" />
<ClInclude Include="..\..\mesh.h" /> <ClInclude Include="..\..\mesh.h" />
<ClInclude Include="..\..\shader.h" /> <ClInclude Include="..\..\shader.h" />
<ClInclude Include="..\..\sound.h" /> <ClInclude Include="..\..\sound.h" />
@@ -32,6 +34,12 @@
<ClInclude Include="..\..\format.h" /> <ClInclude Include="..\..\format.h" />
<ClInclude Include="..\..\trigger.h" /> <ClInclude Include="..\..\trigger.h" />
<ClInclude Include="..\..\utils.h" /> <ClInclude Include="..\..\utils.h" />
<ClInclude Include="..\..\libs\minimp3\minimp3.h">
<Filter>libs\minimp3</Filter>
</ClInclude>
<ClInclude Include="..\..\libs\minimp3\libc.h">
<Filter>libs\minimp3</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="..\..\shaders\depth.glsl"> <None Include="..\..\shaders\depth.glsl">
@@ -60,5 +68,14 @@
<Filter Include="shaders"> <Filter Include="shaders">
<UniqueIdentifier>{3fcb6c00-268a-4570-b6ca-3bf1cf1e10d7}</UniqueIdentifier> <UniqueIdentifier>{3fcb6c00-268a-4570-b6ca-3bf1cf1e10d7}</UniqueIdentifier>
</Filter> </Filter>
<Filter Include="libs">
<UniqueIdentifier>{0400c04a-7900-49a2-9b9d-6667d815168c}</UniqueIdentifier>
</Filter>
<Filter Include="libs\minimp3">
<UniqueIdentifier>{bc55e021-2a62-4bc1-8b80-0fd900f20b2b}</UniqueIdentifier>
</Filter>
<Filter Include="libs\stb_vorbis">
<UniqueIdentifier>{f26df5a9-d32a-4e5e-948c-e632c42be9fa}</UniqueIdentifier>
</Filter>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -28,7 +28,7 @@ int getTime() {
// common input functions // common input functions
InputKey keyToInputKey(int code) { InputKey keyToInputKey(int code) {
static const int codes[] = { static const int codes[] = {
VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN, VK_SPACE, VK_RETURN, VK_ESCAPE, VK_SHIFT, VK_CONTROL, VK_MENU, VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN, VK_SPACE, VK_TAB, VK_RETURN, VK_ESCAPE, VK_SHIFT, VK_CONTROL, VK_MENU,
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',

View File

@@ -56,7 +56,7 @@ struct Shader {
enum Type : GLint { enum Type : GLint {
DEFAULT = 0, DEFAULT = 0,
/* shader */ SPRITE = 0, FLASH = 1, ROOM = 2, ENTITY = 3, MIRROR = 4, /* shader */ SPRITE = 0, FLASH = 1, ROOM = 2, ENTITY = 3, MIRROR = 4,
/* filter */ FILTER_DOWNSAMPLE = 1, /* filter */ FILTER_DOWNSAMPLE = 1, FILTER_GRAYSCALE = 2, FILTER_BLUR = 3, FILTER_MIXER = 4,
/* water */ WATER_DROP = 0, WATER_STEP = 1, WATER_CAUSTICS = 2, WATER_MASK = 3, WATER_COMPOSE = 4, /* water */ WATER_DROP = 0, WATER_STEP = 1, WATER_CAUSTICS = 2, WATER_MASK = 3, WATER_COMPOSE = 4,
MAX = 5 MAX = 5
}; };

View File

@@ -17,10 +17,12 @@ uniform int uType;
} }
#else #else
uniform sampler2D sDiffuse; uniform sampler2D sDiffuse;
uniform vec4 uParam; // texture size uniform sampler2D sNormal;
vec4 downsample() { uniform vec4 uParam;
float k = 1.0 / uParam.x;
vec4 downsample() { // uParam (textureSize, unused, unused, unused)
float k = 1.0 / uParam.x; // inverted texture size
vec4 color = vec4(0.0); vec4 color = vec4(0.0);
for (float y = -1.5; y < 2.0; y++) for (float y = -1.5; y < 2.0; y++)
@@ -35,10 +37,46 @@ uniform int uType;
return vec4(color.xyz / color.w, 1.0); return vec4(color.xyz / color.w, 1.0);
} }
vec4 grayscale() { // uParam (factor, unused, unused, unused)
vec4 color = texture2D(sDiffuse, vTexCoord);
vec3 gray = vec3(dot(color, vec4(0.299, 0.587, 0.114, 0.0)));
return vec4(mix(color.xyz, gray, uParam.x), color.w);
}
vec4 blur() { // uParam (dirX, dirY, 1 / textureSize, unused)
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;
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];
color += texture2D(sDiffuse, vTexCoord + dir * offset[2]) * weight[2];
color += texture2D(sDiffuse, vTexCoord - dir * offset[2]) * weight[2];
return color;
}
vec4 mixer() { // uParam (lerp factor from diffuse to normal textures, multiply, unused, unused)
return mix(texture2D(sDiffuse, vTexCoord), texture2D(sNormal, vTexCoord), uParam.x) * uParam.y;
}
vec4 filter() { vec4 filter() {
#ifdef FILTER_DOWNSAMPLE #ifdef FILTER_DOWNSAMPLE
return downsample(); return downsample();
#endif #endif
#ifdef FILTER_GRAYSCALE
return grayscale();
#endif
#ifdef FILTER_BLUR
return blur();
#endif
#ifdef FILTER_MIXER
return mixer();
#endif
return texture2D(sDiffuse, vTexCoord); return texture2D(sDiffuse, vTexCoord);
} }

View File

@@ -24,7 +24,7 @@ varying vec2 vTexCoord;
uniform vec4 uMaterial; uniform vec4 uMaterial;
void main() { void main() {
gl_FragColor = /* texture2D(sDiffuse, vTexCoord) * */ uMaterial; gl_FragColor = texture2D(sDiffuse, vTexCoord) * uMaterial;
} }
#endif #endif
)====" )===="

207
src/ui.h
View File

@@ -4,137 +4,100 @@
#include "core.h" #include "core.h"
#include "controller.h" #include "controller.h"
struct UI { namespace UI {
enum TouchButton { bNone, bWeapon, bWalk, bAction, bJump, bMAX };
enum TouchZone { zMove, zLook, zButton, zMAX };
IGame *game; IGame *game;
float touchTimerVis, touchTimerTap;
InputKey touch[zMAX];
TouchButton btn;
vec2 btnPos[bMAX];
float btnRadius;
bool doubleTap;
UI(IGame *game) : game(game), touchTimerVis(0.0f), touchTimerTap(0.0f), doubleTap(false) { const static uint8 char_width[110] = {
touch[zMove] = touch[zLook] = touch[zButton] = ikNone; 14, 11, 11, 11, 11, 11, 11, 13, 8, 11, 12, 11, 13, 13, 12, 11, 12, 12, 11, 12, 13, 13, 13, 12,
12, 11, 9, 9, 9, 9, 9, 9, 9, 9, 5, 9, 9, 5, 12, 10, 9, 9, 9, 8, 9, 8, 9, 9, 11, 9, 9, 9, 12, 8,
10, 10, 10, 10, 10, 9, 10, 10, 5, 5, 5, 11, 9, 10, 8, 6, 6, 7, 7, 3, 11, 8, 13, 16, 9, 4, 12, 12,
7, 5, 7, 7, 7, 7, 7, 7, 7, 7, 16, 14, 14, 14, 16, 16, 16, 16, 16, 12, 14, 8, 8, 8, 8, 8, 8, 8 };
static const uint8 char_map[102] = {
0, 64, 66, 78, 77, 74, 78, 79, 69, 70, 92, 72, 63, 71, 62, 68, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, 73, 73, 66, 74, 75, 65, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25, 80, 76, 81, 97, 98, 77, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 100, 101, 102, 67, 0, 0, 0, 0, 0, 0, 0 };
enum Align { aLeft, aRight, aCenter };
inline int charRemap(char c) {
return c > 10 ? (c > 15 ? char_map[c - 32] : c + 91) : c + 81;
} }
bool checkTouchZone(TouchZone zone) { int getTextWidth(const char *text) {
InputKey &t = touch[zone]; int width = 0;
if (t != ikNone && !Input::down[t]) {
t = ikNone; while (char c = *text++)
return true; width += (c == ' ' || c == '_') ? 6 : (char_width[charRemap(c)] + 1);
}
return false; return width - 1;
} }
void getTouchDir(InputKey touch, vec2 &dir) { #define MAX_CHARS 1024
vec2 delta = vec2(0.0f);
if (touch == ikNone)
return;
Input::Touch &t = Input::touch[touch - ikTouchA]; struct {
vec2 d = t.pos - t.start; Vertex vertices[MAX_CHARS * 4];
float len = d.length(); Index indices[MAX_CHARS * 6];
if (len > EPS) int iCount;
delta = d * (min(len / 100.0f, 1.0f) / len); int vCount;
} buffer;
dir = delta; void textBegin() {
buffer.iCount = buffer.vCount = 0;
} }
void getTouchButton(const vec2 &pos) { void textEnd(IGame *game) {
btn = bMAX; if (buffer.iCount > 0) {
float minDist = 1000.0f; game->getMesh()->renderBuffer(buffer.indices, buffer.vertices, buffer.iCount);
for (int i = 0; i < bMAX; i++) { buffer.iCount = buffer.vCount = 0;
float d = (pos - btnPos[i]).length();
if (d < minDist) {
minDist = d;
btn = TouchButton(i);
}
} }
} }
void touchSetDown(bool down) { void textOut(IGame *game, const vec2 &pos, const char *text, Align align = aLeft, float width = 0) {
switch (btn) { if (!text) return;
case bWeapon : Input::setDown(ikJoyY, down); break;
case bWalk : Input::setDown(ikJoyLB, down); break; TR::Level *level = game->getLevel();
case bAction : Input::setDown(ikJoyA, down); break; MeshBuilder *mesh = game->getMesh();
case bJump : Input::setDown(ikJoyX, down); break;
default : ; int seq = level->extra.glyphSeq;
int x = int(pos.x);
int y = int(pos.y);
if (align == aCenter)
x += (int(width) - getTextWidth(text)) / 2;
if (align == aRight)
x += int(width) - getTextWidth(text);
while (char c = *text++) {
if (c == ' ' || c == '_') {
x += 6;
continue;
} }
int frame = charRemap(c);
if (buffer.iCount == MAX_CHARS * 6)
textEnd(game);
TR::SpriteTexture &sprite = level->spriteTextures[level->spriteSequences[seq].sStart + frame];
mesh->addSprite(buffer.indices, buffer.vertices, buffer.iCount, buffer.vCount, 0, x, y, 0, sprite, 255, true);
x += char_width[frame] + 1;
}
}
#undef MAX_CHARS
void init(IGame *game) {
UI::game = game;
} }
void update() { void update() {
if (touchTimerTap > 0.0f) //
touchTimerTap = max(0.0f, touchTimerTap - Core::deltaTime);
if (touch[zMove] != ikNone || touch[zLook] != ikNone || touch[zButton] != ikNone)
touchTimerVis = 30.0f;
else
if (touchTimerVis > 0.0f)
touchTimerVis = max(0.0f, touchTimerVis - Core::deltaTime);
// update buttons
float offset = Core::height * 0.25f;
float radius = offset;
vec2 center = vec2(Core::width - offset * 0.7f, Core::height - offset * 0.7f);
btnPos[bWeapon] = center;
btnPos[bJump] = center + vec2(cos(-PI * 0.5f), sin(-PI * 0.5f)) * radius;
btnPos[bAction] = center + vec2(cos(-PI * 3.0f / 4.0f), sin(-PI * 3.0f / 4.0f)) * radius;
btnPos[bWalk] = center + vec2(cos(-PI), sin(-PI)) * radius;
btnRadius = Core::height * (25.0f / 1080.0f);
// touch update
if (checkTouchZone(zMove))
Input::joy.L = vec2(0.0f);
if (checkTouchZone(zLook))
Input::joy.R = vec2(0.0f);
if (checkTouchZone(zButton)) {
touchSetDown(false);
btn = bNone;
}
if (doubleTap) {
doubleTap = false;
Input::setDown(ikJoyB, false);
}
float zoneSize = Core::width / 3.0f;
for (int i = 0; i < COUNT(Input::touch); i++) {
InputKey key = InputKey(i + ikTouchA);
if (!Input::down[key]) continue;
if (key == touch[zMove] || key == touch[zLook] || key == touch[zButton]) continue;
int zone = clamp(int(Input::touch[i].pos.x / zoneSize), 0, 2);
InputKey &t = touch[zone];
if (t == ikNone) {
t = key;
if (zone == zMove) {
if (touchTimerTap > 0.0f && touchTimerTap < 0.3f) { // 100 ms gap to reduce false positives (bad touch sensors)
doubleTap = true;
touchTimerTap = 0.0f;
} else
touchTimerTap = 0.3f;
}
}
}
// set active touches as gamepad controls
getTouchDir(touch[zMove], Input::joy.L);
getTouchDir(touch[zLook], Input::joy.R);
if (touch[zButton] != ikNone && btn == bNone) {
getTouchButton(Input::touch[touch[zButton] - ikTouchA].pos);
touchSetDown(true);
}
if (doubleTap)
Input::setDown(ikJoyB, true);
} }
void renderControl(const vec2 &pos, float size, bool active) { void renderControl(const vec2 &pos, float size, bool active) {
@@ -144,7 +107,9 @@ struct UI {
} }
void renderTouch() { void renderTouch() {
if (touchTimerVis <= 0.0f) return; game->setupBinding();
if (Input::touchTimerVis <= 0.0f) return;
Core::setDepthTest(false); Core::setDepthTest(false);
Core::setBlending(bmAlpha); Core::setBlending(bmAlpha);
@@ -157,15 +122,15 @@ struct UI {
float offset = Core::height * 0.25f; float offset = Core::height * 0.25f;
vec2 pos = vec2(offset, Core::height - offset); vec2 pos = vec2(offset, Core::height - offset);
if (Input::down[touch[zMove]]) { if (Input::down[Input::touchKey[Input::zMove]]) {
Input::Touch &t = Input::touch[touch[zMove] - ikTouchA]; Input::Touch &t = Input::touch[Input::touchKey[Input::zMove] - ikTouchA];
renderControl(t.pos, btnRadius, true); renderControl(t.pos, Input::btnRadius, true);
pos = t.start; pos = t.start;
} }
renderControl(pos, btnRadius, false); renderControl(pos, Input::btnRadius, false);
for (int i = bWeapon; i < bMAX; i++) for (int i = Input::bWeapon; i < Input::bMAX; i++)
renderControl(btnPos[i], btnRadius, btn == i); renderControl(Input::btnPos[i], Input::btnRadius, Input::btn == i);
Core::setCulling(cfFront); Core::setCulling(cfFront);
Core::setBlending(bmNone); Core::setBlending(bmNone);

View File

@@ -78,12 +78,6 @@ inline void swap(T &a, T &b) {
b = tmp; b = tmp;
} }
float lerp(float a, float b, float t) {
if (t <= 0.0f) return a;
if (t >= 1.0f) return b;
return a + (b - a) * t;
}
float clampAngle(float a) { float clampAngle(float a) {
return a < -PI ? a + PI2 : (a >= PI ? a - PI2 : a); return a < -PI ? a + PI2 : (a >= PI ? a - PI2 : a);
} }
@@ -113,6 +107,22 @@ float decrease(float delta, float &value, float &speed) {
return 0.0f; return 0.0f;
} }
float hermite(float x) {
return x * x * (3.0f - 2.0f * x);
}
float lerp(float a, float b, float t) {
if (t <= 0.0f) return a;
if (t >= 1.0f) return b;
return a + (b - a) * t;
}
float lerpAngle(float a, float b, float t) {
if (t <= 0.0f) return a;
if (t >= 1.0f) return b;
return a + shortAngle(a, b) * t;
}
int nextPow2(uint32 x) { int nextPow2(uint32 x) {
x--; x--;
x |= x >> 1; x |= x >> 1;
@@ -421,8 +431,13 @@ struct mat4 {
mat4(float fov, float aspect, float znear, float zfar) { mat4(float fov, float aspect, float znear, float zfar) {
float k = 1.0f / tanf(fov * 0.5f * DEG2RAD); float k = 1.0f / tanf(fov * 0.5f * DEG2RAD);
identity(); identity();
if (aspect >= 1.0f) {
e00 = k / aspect; e00 = k / aspect;
e11 = k; e11 = k;
} else {
e00 = k;
e11 = k * aspect;
}
e22 = (znear + zfar) / (znear - zfar); e22 = (znear + zfar) / (znear - zfar);
e33 = 0.0f; e33 = 0.0f;
e32 = -1.0f; e32 = -1.0f;