1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-04-22 03:51:58 +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_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_ALPHA_TEST);
@ -106,16 +109,16 @@ struct ShaderCache {
char def[1024], ext[255];
ext[0] = 0;
if (Core::settings.shadows) {
if (Core::support.shadowSampler) {
#ifdef MOBILE
strcat(ext, "#extension GL_EXT_shadow_samplers : require\n");
#endif
strcat(ext, "#define SHADOW_SAMPLER\n");
} else {
if (Core::support.depthTexture)
strcat(ext, "#define SHADOW_DEPTH\n");
else
strcat(ext, "#define SHADOW_COLOR\n");
if (Core::support.shadowSampler) {
#ifdef MOBILE
strcat(ext, "#extension GL_EXT_shadow_samplers : require\n");
#endif
strcat(ext, "#define SHADOW_SAMPLER\n");
} else {
if (Core::support.depthTexture)
strcat(ext, "#define SHADOW_DEPTH\n");
else
strcat(ext, "#define SHADOW_COLOR\n");
}
}
@ -153,7 +156,7 @@ struct ShaderCache {
break;
}
case Core::passFilter : {
static const char *typeNames[] = { "DEFAULT", "DOWNSAMPLE" };
static const char *typeNames[] = { "DEFAULT", "DOWNSAMPLE", "GRAYSCALE", "BLUR", "MIXER" };
src = FILTER;
typ = typeNames[type];
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];
if (!item.visible) continue;
item.mask->bind(sMask);
if (item.timer >= SIMULATE_TIMESTEP || dropCount) {
item.mask->bind(sMask);
// add water drops
drop(item);
// simulation step

View File

@ -277,7 +277,8 @@ struct Camera : Controller {
}
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) {

View File

@ -90,16 +90,16 @@ struct Collision {
}
inline float getOffset(float from, float to) {
int a = int(from) / 1024;
int b = int(to) / 1024;
int a = int(from) / 1024;
int b = int(to) / 1024;
from -= float(a * 1024.0f);
from -= float(a * 1024.0f);
if (b == a)
return 0.0f;
if (b == a)
return 0.0f;
else if (b > a)
return -from + 1025.0f;
return -from - 1.0f;
return -from + 1025.0f;
return -from - 1.0f;
}
static int getFloor(TR::Level *level, int &roomIndex, const vec3 &pos) {

View File

@ -26,9 +26,15 @@ struct IGame {
virtual void updateParams() {}
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 setupBinding() {}
virtual void renderEnvironment(int roomIndex, const vec3 &pos, Texture **targets, int stride = 0) {}
virtual void renderCompose(int roomIndex, bool genShadowMask = false) {}
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 {
@ -181,25 +187,7 @@ struct Controller {
}
Sound::Sample* playSound(int id, const vec3 &pos, int flags) 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, entity * 1000 + index);
}
return NULL;
return game->playSound(id, pos, flags, entity);
}
vec3 getDir() const {

View File

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

View File

@ -173,13 +173,13 @@ namespace Debug {
}
void text(const vec3 &pos, const vec4 &color, const char *str) {
vec4 p = Core::mViewProj * vec4(pos, 1);
if (p.w > 0) {
p.xyz = p.xyz * (1.0f / p.w);
p.y = -p.y;
p.xyz = (p.xyz * 0.5f + vec3(0.5f)) * vec3(Core::width, Core::height, 1.0f);
vec4 p = Core::mViewProj * vec4(pos, 1);
if (p.w > 0) {
p.xyz = p.xyz * (1.0f / p.w);
p.y = -p.y;
p.xyz = (p.xyz * 0.5f + vec3(0.5f)) * vec3(Core::width, Core::height, 1.0f);
text(vec2(p.x, p.y), color, str);
}
}
}
}

View File

@ -80,7 +80,7 @@
E( BRIDGE_0 ) \
E( BRIDGE_1 ) \
E( BRIDGE_2 ) \
E( INV_GAME ) \
E( INV_PASSPORT ) \
E( INV_COMPASS ) \
E( INV_HOME ) \
E( GEARS_1 ) \
@ -90,7 +90,7 @@
E( CUT_2 ) \
E( CUT_3 ) \
E( CUT_4 ) \
E( INV_GAME_CLOSED ) \
E( INV_PASSPORT_CLOSED ) \
E( INV_MAP ) \
E( CRYSTAL ) \
E( WEAPON_PISTOLS ) \
@ -107,12 +107,12 @@
E( INV_DETAIL ) \
E( INV_SOUND ) \
E( INV_CONTROLS ) \
E( INV_FLASHLIGHT ) \
E( INV_GAMMA ) \
E( INV_PISTOLS ) \
E( INV_SHOTGUN ) \
E( INV_MAGNUMS ) \
E( INV_UZIS ) \
E( INV_AMMO_POSTOLS ) \
E( INV_AMMO_PISTOLS ) \
E( INV_AMMO_SHOTGUN ) \
E( INV_AMMO_MAGNUMS ) \
E( INV_AMMO_UZIS ) \
@ -291,14 +291,14 @@ namespace TR {
SND_UNDERWATER = 60,
SND_MENU_SPIN = 108,
SND_MENU_HOME = 109,
SND_MENU_CONTROLS = 110,
SND_MENU_SHOW = 111,
SND_MENU_HIDE = 112,
SND_MENU_COMPASS = 113,
SND_MENU_WEAPON = 114,
SND_MENU_PAGE = 115,
SND_INV_SPIN = 108,
SND_INV_HOME = 109,
SND_INV_CONTROLS = 110,
SND_INV_SHOW = 111,
SND_INV_HIDE = 112,
SND_INV_COMPASS = 113,
SND_INV_WEAPON = 114,
SND_INV_PAGE = 115,
SND_HEALTH = 116,
SND_DART = 151,
@ -987,6 +987,29 @@ namespace TR {
int16 puzzleSet;
int16 weapons[4];
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;
Level(Stream &stream, bool demo) {
@ -1201,22 +1224,64 @@ namespace TR {
// init secrets states
memset(secrets, 0, MAX_SECRETS_COUNT * sizeof(secrets[0]));
// get special models indices
memset(&extra, 0, sizeof(extra));
for (int i = 0; i < 4; i++)
extra.weapons[i] = -1;
extra.braid = -1;
memset(&extra, 0xFF, sizeof(extra));
for (int i = 0; i < modelsCount; i++)
switch (models[i].type) {
case Entity::MUZZLE_FLASH : extra.muzzleFlash = i; break;
case Entity::PUZZLE_DONE_1 : extra.puzzleSet = i; break;
case Entity::LARA_PISTOLS : extra.weapons[0] = i; break;
case Entity::LARA_SHOTGUN : extra.weapons[1] = i; break;
case Entity::LARA_MAGNUMS : extra.weapons[2] = i; break;
case Entity::LARA_UZIS : extra.weapons[3] = i; break;
case Entity::BRAID : extra.braid = i; break;
case Entity::MUZZLE_FLASH : extra.muzzleFlash = i; break;
case Entity::PUZZLE_DONE_1 : extra.puzzleSet = i; break;
case Entity::LARA_PISTOLS : extra.weapons[0] = i; break;
case Entity::LARA_SHOTGUN : extra.weapons[1] = i; break;
case Entity::LARA_MAGNUMS : extra.weapons[2] = i; break;
case Entity::LARA_UZIS : extra.weapons[3] = 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 : ;
}
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
cutMatrix.identity();
if (cutEntity > -1) {
@ -1665,7 +1730,7 @@ namespace TR {
return Color24(255, 0, 255);
}
Stream* getSampleStream(int index) {
Stream* getSampleStream(int index) const {
uint8 *data = &soundData[soundOffsets[index]];
uint32 size = 0;
switch (version) {

View File

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

View File

@ -5,7 +5,7 @@
enum InputKey { ikNone,
// 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,
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,
@ -15,12 +15,55 @@ enum InputKey { ikNone,
ikTouchA, ikTouchB, ikTouchC, ikTouchD, ikTouchE, ikTouchF,
// gamepad
ikJoyA, ikJoyB, ikJoyX, ikJoyY, ikJoyLB, ikJoyRB, ikJoySelect, ikJoyStart, ikJoyL, ikJoyR, ikJoyLT, ikJoyRT, ikJoyPOV,
ikJoyLeft, ikJoyRight, ikJoyUp, ikJoyDown,
ikMAX };
enum ControlKey { cLeft, cRight, cUp, cDown, cJump, cWalk, cAction, cWeapon, cLook, cStepLeft, cStepRight, cRoll, cInventory, cMAX };
namespace Input {
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 {
vec2 pos;
struct {
@ -31,7 +74,7 @@ namespace Input {
struct {
vec2 L, R;
float LT, RT;
int POV;
int POV;
} joy;
struct Touch {
@ -68,13 +111,15 @@ namespace Input {
}
} head;
void reset() {
memset(down, 0, sizeof(down));
memset(&mouse, 0, sizeof(mouse));
memset(&joy, 0, sizeof(joy));
memset(&touch, 0, sizeof(touch));
head.reset();
}
enum TouchButton { bNone, bWeapon, bWalk, bAction, bJump, bMAX };
enum TouchZone { zMove, zLook, zButton, zMAX };
float touchTimerVis, touchTimerTap;
InputKey touchKey[zMAX];
TouchButton btn;
vec2 btnPos[bMAX];
float btnRadius;
bool doubleTap;
void setDown(InputKey key, bool value) {
if (down[key] == value)
@ -118,17 +163,158 @@ namespace Input {
}
InputKey getTouch(int id) {
for (int i = 0; i < COUNT(touch); i++)
for (int i = 0; i < COUNT(touch); i++)
if (down[ikTouchA + i] && touch[i].id == id)
return InputKey(ikTouchA + i);
for (int i = 0; i < COUNT(touch); i++)
for (int i = 0; i < COUNT(touch); i++)
if (!down[ikTouchA + i]) {
touch[i].id = id;
return InputKey(ikTouchA + i);
}
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;
}
}

View File

@ -2,18 +2,175 @@
#define H_INVENTORY
#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 {
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 {
TR::Entity::Type type;
int count;
} items[MAX_ITEMS];
int itemsCount;
float angle;
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) {
for (int i = 0; i < itemsCount; i++)
@ -29,11 +186,21 @@ struct Inventory {
return;
}
if(itemsCount < MAX_ITEMS) {
items[itemsCount].type = type;
items[itemsCount].count = count;
itemsCount++;
ASSERT(itemsCount < INVENTORY_MAX_ITEMS);
int pos = 0;
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) {
@ -47,6 +214,364 @@ struct Inventory {
if (i > -1)
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

View File

@ -6,7 +6,6 @@
#include "character.h"
#include "trigger.h"
#include "sprite.h"
#include "inventory.h"
#define TURN_FAST PI
#define TURN_FAST_BACK PI * 3.0f / 4.0f
@ -21,6 +20,8 @@
#define LARA_TILT_SPEED (DEG2RAD * 37.5f)
#define LARA_TILT_MAX (DEG2RAD * 10.0f)
#define LARA_MAX_HEALTH 1000
#define LARA_HANG_OFFSET 724
#define LARA_HEIGHT 762
#define LARA_HEIGHT_WATER 400
@ -213,7 +214,6 @@ struct Lara : Character {
ActionCommand actionList[MAX_TRIGGER_ACTIONS];
Inventory inventory;
int lastPickUp;
int viewTarget;
int roomPrev; // water out from room
@ -384,7 +384,7 @@ struct Lara : Character {
} *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 (getRoom().flags.water)
@ -399,7 +399,11 @@ struct Lara : Character {
getEntity().flags.active = 1;
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) {
weapons[Weapon::PISTOLS].ammo = 0;
weapons[Weapon::SHOTGUN].ammo = 9000;
@ -666,7 +670,11 @@ struct Lara : Character {
}
void wpnChange(Weapon::Type wType) {
if (wpnCurrent == wType || home) return;
if (wpnCurrent == wType || home) {
if (emptyHands())
wpnDraw();
return;
}
wpnNext = wType;
wpnHide();
}
@ -803,11 +811,6 @@ struct Lara : Character {
updateTargets();
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()) {
wpnSet(wpnNext);
wpnDraw();
@ -1233,6 +1236,21 @@ struct Lara : Character {
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() {
// TODO: playSound 36
if (collision.side != Collision::FRONT || pos.y - collision.info[Collision::FRONT].floor > 256 + 128)
@ -1298,22 +1316,6 @@ struct Lara : Character {
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) {
if (ay)
angle = item->angle;
@ -1390,7 +1392,7 @@ struct Lara : Character {
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)))
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);
return;
}
@ -1884,32 +1886,21 @@ struct Lara : Character {
input = Character::getInput();
if (input & DEATH) return input;
int &p = Input::joy.POV;
#ifndef LEVEL_EDITOR
if (Input::down[ikW]) input |= FORTH;
if (Input::down[ikD]) input |= RIGHT;
if (Input::down[ikS]) input |= BACK;
if (Input::down[ikA]) input |= LEFT;
#endif
if (Input::down[ikUp] || p == 8 || p == 1 || p == 2) input |= FORTH;
if (Input::down[ikRight] || p == 2 || p == 3 || p == 4) input |= RIGHT;
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;
if (Input::state[cUp]) input |= FORTH;
if (Input::state[cRight]) input |= RIGHT;
if (Input::state[cDown]) input |= BACK;
if (Input::state[cLeft]) input |= LEFT;
if (Input::state[cRoll]) input = FORTH | BACK;
if (Input::state[cStepRight]) input = WALK | RIGHT;
if (Input::state[cStepLeft]) input = WALK | LEFT;
if (Input::state[cJump]) input |= JUMP;
if (Input::state[cWalk]) input |= WALK;
if (Input::state[cAction]) input |= ACTION;
if (Input::state[cWeapon]) input |= WEAPON;
// analog control
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)
return input;
@ -1945,7 +1936,7 @@ struct Lara : Character {
int pickupFrame = stand == STAND_GROUND ? PICKUP_FRAME_GROUND : PICKUP_FRAME_UNDERWATER;
if (animation.isFrameActive(pickupFrame)) {
item.flags.invisible = true;
inventory.add(item.type, 1);
game->invAdd(item.type, 1);
}
}
break;

View File

@ -9,6 +9,7 @@
#include "enemy.h"
#include "camera.h"
#include "trigger.h"
#include "inventory.h"
#ifdef _DEBUG
#include "debug.h"
@ -16,6 +17,7 @@
struct Level : IGame {
TR::Level level;
Inventory inventory;
Texture *atlas;
Texture *cube;
MeshBuilder *mesh;
@ -36,8 +38,11 @@ struct Level : IGame {
WaterCache *waterCache;
ZoneCache *zoneCache;
Sound::Sample *sndAmbient;
Sound::Sample *sndSoundtrack;
Sound::Sample *sndUnderwater;
Sound::Sample *sndCurrent;
bool lastTitle;
// IGame implementation ========
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));
}
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) {
PROFILE_MARKER("ENVIRONMENT");
Core::eye = 0.0f;
@ -111,9 +126,40 @@ struct Level : IGame {
virtual void fxQuake(float 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;
#ifdef _DEBUG
@ -241,29 +287,44 @@ struct Level : IGame {
}
}
ASSERT(lara != NULL);
camera = new Camera(this, lara);
lastTitle = false;
level.cameraController = camera;
if (!isTitle()) {
ASSERT(lara != NULL);
camera = new Camera(this, lara);
ambientCache = Core::settings.ambient ? new AmbientCache(this) : NULL;
waterCache = Core::settings.water ? new WaterCache(this) : NULL;
zoneCache = new ZoneCache(this);
shadow = Core::settings.shadows ? new Texture(SHADOW_TEX_SIZE, SHADOW_TEX_SIZE, Texture::SHADOW, false) : NULL;
level.cameraController = camera;
initReflections();
ambientCache = Core::settings.ambient ? new AmbientCache(this) : NULL;
waterCache = Core::settings.water ? new WaterCache(this) : NULL;
zoneCache = new ZoneCache(this);
shadow = Core::settings.shadows ? new Texture(SHADOW_TEX_SIZE, SHADOW_TEX_SIZE, Texture::SHADOW, false) : NULL;
// init sounds
//Sound::play(Sound::openWAD("05_Lara's_Themes.wav"), 1, 1, 0);
sndAmbient = Sound::play(snd, vec3(0.0f), 1, 1, Sound::Flags::LOOP);
initReflections();
sndUnderwater = lara->playSound(TR::SND_UNDERWATER, vec3(0.0f), Sound::LOOP);
if (sndUnderwater)
sndUnderwater->volume = 0.0f;
// init sounds
//Sound::play(Sound::openWAD("05_Lara's_Themes.wav"), 1, 1, 0);
sndSoundtrack = Sound::play(snd, vec3(0.0f), 1, 1, Sound::Flags::LOOP);
for (int i = 0; i < level.soundSourcesCount; 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);
sndUnderwater = lara->playSound(TR::SND_UNDERWATER, vec3(0.0f), Sound::LOOP);
if (sndUnderwater)
sndUnderwater->volume = 0.0f;
sndCurrent = sndSoundtrack;
for (int i = 0; i < level.soundSourcesCount; 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);
}
} else {
camera = NULL;
ambientCache = NULL;
waterCache = NULL;
zoneCache = NULL;
shadow = NULL;
sndSoundtrack = NULL;
sndUnderwater = NULL;
sndCurrent = NULL;
}
}
@ -289,6 +350,10 @@ struct Level : IGame {
Sound::stopAll();
}
bool isTitle() {
return lara == NULL || inventory.isActive();
}
void initTextures() {
if (!level.tilesCount) {
atlas = NULL;
@ -554,6 +619,13 @@ struct Level : IGame {
lara->reset(TR::NO_ROOM, camera->pos, camera->angle.y, false);
}
#endif
if (isTitle()) {
sndCurrent->volume = 0.0f;
Sound::reverb.setRoomSize(vec3(1.0f));
inventory.update();
return;
}
params->time += Core::deltaTime;
for (int i = 0; i < level.entitiesCount; i++) {
@ -572,9 +644,14 @@ struct Level : IGame {
}
camera->update();
float ambientVolume = camera->isUnderwater() ? 0.0f : 1.0f;
if (sndAmbient) sndAmbient->volume = ambientVolume;
if (sndUnderwater) sndUnderwater->volume = 1.0f - ambientVolume;
sndCurrent = camera->isUnderwater() ? sndUnderwater : sndSoundtrack;
if (sndCurrent) {
if (sndSoundtrack && sndCurrent != sndSoundtrack) sndSoundtrack->volume = 0.0f;
if (sndUnderwater && sndCurrent != sndUnderwater) sndUnderwater->volume = 0.0f;
sndCurrent->volume = 1.0f;
}
if (waterCache)
waterCache->update();
@ -585,16 +662,7 @@ struct Level : IGame {
camera->setup(Core::pass == Core::passCompose);
atlas->bind(sDiffuse);
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();
setupBinding();
// clear visibility flag for rooms
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));
}
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
void renderDebug() {
// Core::mViewInv = camera->mViewInv;
// Core::mView = Core::mViewInv.inverse();
Core::setViewport(0, 0, Core::width, Core::height);
@ -868,107 +905,74 @@ struct Level : IGame {
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::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

View File

@ -21,8 +21,13 @@ struct MeshRange {
MeshRange() : aIndex(-1) {}
void setup() const {
Vertex *v = (Vertex*)(vStart * sizeof(Vertex));
void setup(Vertex *offset) const {
glEnableVertexAttribArray(aCoord);
glEnableVertexAttribArray(aTexCoord);
glEnableVertexAttribArray(aNormal);
glEnableVertexAttribArray(aColor);
Vertex *v = (Vertex*)(offset + vStart);
glVertexAttribPointer(aCoord, 4, GL_SHORT, false, sizeof(Vertex), &v->coord);
glVertexAttribPointer(aTexCoord, 4, GL_SHORT, false, sizeof(Vertex), &v->texCoord);
glVertexAttribPointer(aNormal, 4, GL_SHORT, true, sizeof(Vertex), &v->normal);
@ -30,12 +35,9 @@ struct MeshRange {
}
void bind(GLuint *VAO) const {
if (aIndex > -1) {
if (Core::active.VAO != VAO[aIndex]) {
glBindVertexArray(Core::active.VAO = VAO[aIndex]);
}
} else
setup();
GLuint vao = aIndex == -1 ? 0 : VAO[aIndex];
if (Core::support.VAO && Core::active.VAO != vao)
glBindVertexArray(Core::active.VAO = vao);
}
};
@ -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) {
glGenBuffers(2, ID);
bind();
bind(true);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, iCount * sizeof(Index), indices, GL_STATIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, vCount * sizeof(Vertex), vertices, GL_STATIC_DRAW);
@ -74,40 +76,50 @@ struct Mesh {
if (Core::support.VAO) {
range.aIndex = aIndex++;
range.bind(VAO);
bind();
range.setup();
bind(true);
range.setup(NULL);
} else
range.aIndex = -1;
}
void bind() {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ID[0]);
glBindBuffer(GL_ARRAY_BUFFER, ID[1]);
glEnableVertexAttribArray(aCoord);
glEnableVertexAttribArray(aTexCoord);
glEnableVertexAttribArray(aNormal);
glEnableVertexAttribArray(aColor);
void bind(bool force = false) {
if (force || Core::active.iBuffer != ID[0])
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, Core::active.iBuffer = ID[0]);
if (force || Core::active.vBuffer != ID[1])
glBindBuffer(GL_ARRAY_BUFFER, Core::active.vBuffer = ID[1]);
}
void DIP(const MeshRange &range) {
glDrawElements(GL_TRIANGLES, range.iCount, GL_UNSIGNED_SHORT, (GLvoid*)(range.iStart * sizeof(Index)));
void unbind() {
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.tris += range.iCount / 3;
}
void render(const MeshRange &range) {
void render(const MeshRange &range, Index *iOffset = NULL, Vertex *vOffset = NULL) {
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) {
Core::setCulling(cfBack);
glStencilOp(GL_KEEP, GL_DECR, GL_KEEP);
DIP(range);
DIP(range, iOffset);
Core::setCulling(cfFront);
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);
}
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);
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].color = quad[1].color = quad[2].color = quad[3].color = { 255, 255, 255, intensity };
int tx = (sprite.tile % 4) * 256;
int ty = (sprite.tile / 4) * 256;
int16 u0 = ((tx + sprite.texCoord[0].x + 1) << 5);
int16 v0 = ((ty + sprite.texCoord[0].y + 1) << 5);
int16 u1 = ((tx + sprite.texCoord[1].x - 1) << 5);
int16 v1 = ((ty + sprite.texCoord[1].y - 1) << 5);
int16 u0 = (((tx + sprite.texCoord[0].x) << 5));
int16 v0 = (((ty + sprite.texCoord[0].y) << 5));
int16 u1 = (((tx + sprite.texCoord[1].x) << 5));
int16 v1 = (((ty + sprite.texCoord[1].y) << 5));
quad[0].texCoord = { u0, v0, sprite.l, sprite.t };
quad[1].texCoord = { u1, v0, sprite.r, sprite.t };
@ -932,6 +961,15 @@ struct MeshBuilder {
void 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) {
ASSERT(rooms[roomIndex].geometry[transparent].iCount > 0);
@ -1000,57 +1038,6 @@ struct MeshBuilder {
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

View File

@ -98,7 +98,7 @@ int getPOV(int x, int y) {
InputKey keyToInputKey(int code) {
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,
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,

View File

@ -78,7 +78,7 @@ int getTime() {
InputKey keyToInputKey(int code) {
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,
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,

View File

@ -42,7 +42,7 @@ void soundInit() {
// common input functions
InputKey keyToInputKey(int code) {
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
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

View File

@ -142,14 +142,14 @@
<!-- <label for="browseFile">Browse Level</label> -->
<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>
<br><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>
<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><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>
Change view: V<br>
Time Control: R - slow motion, T - fast motion<br>
FullScreen: Alt + Enter
</p>
</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>

View File

@ -178,7 +178,7 @@ void changeWindowMode() {
InputKey keyToInputKey(int code) {
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',
'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',

View File

@ -1,9 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="..\..\libs\minimp3\minimp3.cpp" />
<ClCompile Include="..\..\libs\stb_vorbis\stb_vorbis.c" />
<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>
<ClInclude Include="..\..\animation.h" />
@ -22,8 +26,6 @@
<ClInclude Include="..\..\inventory.h" />
<ClInclude Include="..\..\lara.h" />
<ClInclude Include="..\..\level.h" />
<ClInclude Include="..\..\libs\minimp3\libc.h" />
<ClInclude Include="..\..\libs\minimp3\minimp3.h" />
<ClInclude Include="..\..\mesh.h" />
<ClInclude Include="..\..\shader.h" />
<ClInclude Include="..\..\sound.h" />
@ -32,6 +34,12 @@
<ClInclude Include="..\..\format.h" />
<ClInclude Include="..\..\trigger.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>
<None Include="..\..\shaders\depth.glsl">
@ -60,5 +68,14 @@
<Filter Include="shaders">
<UniqueIdentifier>{3fcb6c00-268a-4570-b6ca-3bf1cf1e10d7}</UniqueIdentifier>
</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>
</Project>

View File

@ -28,7 +28,7 @@ int getTime() {
// common input functions
InputKey keyToInputKey(int code) {
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',
'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',

View File

@ -56,7 +56,7 @@ struct Shader {
enum Type : GLint {
DEFAULT = 0,
/* 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,
MAX = 5
};

View File

@ -1,7 +1,7 @@
R"====(
#ifdef GL_ES
precision highp int;
precision highp float;
precision highp int;
precision highp float;
#endif
varying vec2 vTexCoord;
@ -9,41 +9,79 @@ varying vec2 vTexCoord;
uniform int uType;
#ifdef VERTEX
attribute vec4 aCoord;
attribute vec4 aCoord;
void main() {
vTexCoord = aCoord.zw;
gl_Position = vec4(aCoord.xy, 0.0, 1.0);
}
void main() {
vTexCoord = aCoord.zw;
gl_Position = vec4(aCoord.xy, 0.0, 1.0);
}
#else
uniform sampler2D sDiffuse;
uniform vec4 uParam; // texture size
uniform sampler2D sDiffuse;
uniform sampler2D sNormal;
vec4 downsample() {
float k = 1.0 / uParam.x;
uniform vec4 uParam;
vec4 color = vec4(0.0);
for (float y = -1.5; y < 2.0; y++)
for (float x = -1.5; x < 2.0; x++) {
vec4 p;
p.xyz = texture2D(sDiffuse, vTexCoord + vec2(x, y) * k).xyz;
p.w = dot(p.xyz, vec3(0.299, 0.587, 0.114));
p.xyz *= p.w;
color += p;
}
vec4 downsample() { // uParam (textureSize, unused, unused, unused)
float k = 1.0 / uParam.x; // inverted texture size
return vec4(color.xyz / color.w, 1.0);
}
vec4 filter() {
#ifdef FILTER_DOWNSAMPLE
return downsample();
#endif
return texture2D(sDiffuse, vTexCoord);
}
void main() {
gl_FragColor = filter();
}
vec4 color = vec4(0.0);
for (float y = -1.5; y < 2.0; y++)
for (float x = -1.5; x < 2.0; x++) {
vec4 p;
p.xyz = texture2D(sDiffuse, vTexCoord + vec2(x, y) * k).xyz;
p.w = dot(p.xyz, vec3(0.299, 0.587, 0.114));
p.xyz *= p.w;
color += p;
}
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() {
#ifdef FILTER_DOWNSAMPLE
return downsample();
#endif
#ifdef FILTER_GRAYSCALE
return grayscale();
#endif
#ifdef FILTER_BLUR
return blur();
#endif
#ifdef FILTER_MIXER
return mixer();
#endif
return texture2D(sDiffuse, vTexCoord);
}
void main() {
gl_FragColor = filter();
}
#endif
)===="

View File

@ -1,30 +1,30 @@
R"====(
#ifdef GL_ES
precision lowp int;
precision highp float;
precision lowp int;
precision highp float;
#endif
varying vec2 vTexCoord;
#ifdef VERTEX
uniform mat4 uViewProj;
uniform vec4 uPosScale;
uniform mat4 uViewProj;
uniform vec4 uPosScale;
attribute vec4 aCoord;
attribute vec4 aTexCoord;
attribute vec4 aCoord;
attribute vec4 aTexCoord;
#define TEXCOORD_SCALE (1.0 / 32767.0)
void main() {
vTexCoord = aTexCoord.xy * TEXCOORD_SCALE;
gl_Position = uViewProj * vec4(aCoord.xy * uPosScale.zw + uPosScale.xy, 0.0, 1.0);
}
#define TEXCOORD_SCALE (1.0 / 32767.0)
void main() {
vTexCoord = aTexCoord.xy * TEXCOORD_SCALE;
gl_Position = uViewProj * vec4(aCoord.xy * uPosScale.zw + uPosScale.xy, 0.0, 1.0);
}
#else
uniform sampler2D sDiffuse;
uniform vec4 uMaterial;
uniform sampler2D sDiffuse;
uniform vec4 uMaterial;
void main() {
gl_FragColor = /* texture2D(sDiffuse, vTexCoord) * */ uMaterial;
}
void main() {
gl_FragColor = texture2D(sDiffuse, vTexCoord) * uMaterial;
}
#endif
)===="

207
src/ui.h
View File

@ -4,137 +4,100 @@
#include "core.h"
#include "controller.h"
struct UI {
enum TouchButton { bNone, bWeapon, bWalk, bAction, bJump, bMAX };
enum TouchZone { zMove, zLook, zButton, zMAX };
namespace UI {
IGame *game;
IGame *game;
float touchTimerVis, touchTimerTap;
InputKey touch[zMAX];
TouchButton btn;
vec2 btnPos[bMAX];
float btnRadius;
bool doubleTap;
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 };
UI(IGame *game) : game(game), touchTimerVis(0.0f), touchTimerTap(0.0f), doubleTap(false) {
touch[zMove] = touch[zLook] = touch[zButton] = ikNone;
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) {
InputKey &t = touch[zone];
if (t != ikNone && !Input::down[t]) {
t = ikNone;
return true;
int getTextWidth(const char *text) {
int width = 0;
while (char c = *text++)
width += (c == ' ' || c == '_') ? 6 : (char_width[charRemap(c)] + 1);
return width - 1;
}
#define MAX_CHARS 1024
struct {
Vertex vertices[MAX_CHARS * 4];
Index indices[MAX_CHARS * 6];
int iCount;
int vCount;
} buffer;
void textBegin() {
buffer.iCount = buffer.vCount = 0;
}
void textEnd(IGame *game) {
if (buffer.iCount > 0) {
game->getMesh()->renderBuffer(buffer.indices, buffer.vertices, buffer.iCount);
buffer.iCount = buffer.vCount = 0;
}
return false;
}
void getTouchDir(InputKey touch, vec2 &dir) {
vec2 delta = vec2(0.0f);
if (touch == ikNone)
return;
void textOut(IGame *game, const vec2 &pos, const char *text, Align align = aLeft, float width = 0) {
if (!text) return;
TR::Level *level = game->getLevel();
MeshBuilder *mesh = game->getMesh();
Input::Touch &t = Input::touch[touch - ikTouchA];
vec2 d = t.pos - t.start;
float len = d.length();
if (len > EPS)
delta = d * (min(len / 100.0f, 1.0f) / len);
int seq = level->extra.glyphSeq;
dir = delta;
}
int x = int(pos.x);
int y = int(pos.y);
void getTouchButton(const vec2 &pos) {
btn = bMAX;
float minDist = 1000.0f;
for (int i = 0; i < bMAX; i++) {
float d = (pos - btnPos[i]).length();
if (d < minDist) {
minDist = d;
btn = TouchButton(i);
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;
}
}
void touchSetDown(bool down) {
switch (btn) {
case bWeapon : Input::setDown(ikJoyY, down); break;
case bWalk : Input::setDown(ikJoyLB, down); break;
case bAction : Input::setDown(ikJoyA, down); break;
case bJump : Input::setDown(ikJoyX, down); break;
default : ;
}
#undef MAX_CHARS
void init(IGame *game) {
UI::game = game;
}
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) {
@ -144,7 +107,9 @@ struct UI {
}
void renderTouch() {
if (touchTimerVis <= 0.0f) return;
game->setupBinding();
if (Input::touchTimerVis <= 0.0f) return;
Core::setDepthTest(false);
Core::setBlending(bmAlpha);
@ -157,15 +122,15 @@ struct UI {
float offset = Core::height * 0.25f;
vec2 pos = vec2(offset, Core::height - offset);
if (Input::down[touch[zMove]]) {
Input::Touch &t = Input::touch[touch[zMove] - ikTouchA];
renderControl(t.pos, btnRadius, true);
if (Input::down[Input::touchKey[Input::zMove]]) {
Input::Touch &t = Input::touch[Input::touchKey[Input::zMove] - ikTouchA];
renderControl(t.pos, Input::btnRadius, true);
pos = t.start;
}
renderControl(pos, btnRadius, false);
renderControl(pos, Input::btnRadius, false);
for (int i = bWeapon; i < bMAX; i++)
renderControl(btnPos[i], btnRadius, btn == i);
for (int i = Input::bWeapon; i < Input::bMAX; i++)
renderControl(Input::btnPos[i], Input::btnRadius, Input::btn == i);
Core::setCulling(cfFront);
Core::setBlending(bmNone);

View File

@ -78,12 +78,6 @@ inline void swap(T &a, T &b) {
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) {
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;
}
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) {
x--;
x |= x >> 1;
@ -421,8 +431,13 @@ struct mat4 {
mat4(float fov, float aspect, float znear, float zfar) {
float k = 1.0f / tanf(fov * 0.5f * DEG2RAD);
identity();
e00 = k / aspect;
e11 = k;
if (aspect >= 1.0f) {
e00 = k / aspect;
e11 = k;
} else {
e00 = k;
e11 = k * aspect;
}
e22 = (znear + zfar) / (znear - zfar);
e33 = 0.0f;
e32 = -1.0f;