From bbf6b8a3c5cf96f0fc59e2ab6eadf2c7f22b9baa Mon Sep 17 00:00:00 2001 From: XProger Date: Mon, 19 Jun 2017 04:42:00 +0300 Subject: [PATCH] #11 inventory screen; #27 unified controls preset (old school as default) --- src/cache.h | 28 +- src/camera.h | 3 +- src/collision.h | 14 +- src/controller.h | 26 +- src/core.h | 66 ++- src/debug.h | 12 +- src/format.h | 113 +++- src/game.h | 20 +- src/input.h | 210 ++++++- src/inventory.h | 541 +++++++++++++++++- src/lara.h | 91 ++- src/level.h | 324 +++++------ src/mesh.h | 149 +++-- .../android/app/src/main/cpp/main.cpp | 2 +- src/platform/nix/main.cpp | 2 +- src/platform/osx/main.cpp | 2 +- src/platform/web/index.html | 8 +- src/platform/web/main.cpp | 2 +- src/platform/win/OpenLara.vcxproj.filters | 25 +- src/platform/win/main.cpp | 2 +- src/shader.h | 2 +- src/shaders/filter.glsl | 104 ++-- src/shaders/gui.glsl | 34 +- src/ui.h | 207 +++---- src/utils.h | 31 +- 25 files changed, 1412 insertions(+), 606 deletions(-) diff --git a/src/cache.h b/src/cache.h index 1d85636..670d756 100644 --- a/src/cache.h +++ b/src/cache.h @@ -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 diff --git a/src/camera.h b/src/camera.h index cbff22a..94c6df8 100644 --- a/src/camera.h +++ b/src/camera.h @@ -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) { diff --git a/src/collision.h b/src/collision.h index e2d5535..e9bb4aa 100644 --- a/src/collision.h +++ b/src/collision.h @@ -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) { diff --git a/src/controller.h b/src/controller.h index c5a898a..c4a8a13 100644 --- a/src/controller.h +++ b/src/controller.h @@ -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 { diff --git a/src/core.h b/src/core.h index e8297aa..b5f1cce 100644 --- a/src/core.h +++ b/src/core.h @@ -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) diff --git a/src/debug.h b/src/debug.h index 1e19566..b05da50 100644 --- a/src/debug.h +++ b/src/debug.h @@ -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); - } + } } } diff --git a/src/format.h b/src/format.h index 9072c80..429b28e 100644 --- a/src/format.h +++ b/src/format.h @@ -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) { diff --git a/src/game.h b/src/game.h index 76846c9..05b41c9 100644 --- a/src/game.h +++ b/src/game.h @@ -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(); } } diff --git a/src/input.h b/src/input.h index 1eddef2..a25e03e 100644 --- a/src/input.h +++ b/src/input.h @@ -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; } } diff --git a/src/inventory.h b/src/inventory.h index 10fc2b6..99e3746 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -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 \ No newline at end of file diff --git a/src/lara.h b/src/lara.h index f9b11fb..efe94e2 100644 --- a/src/lara.h +++ b/src/lara.h @@ -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; diff --git a/src/level.h b/src/level.h index 628cb69..896765e 100644 --- a/src/level.h +++ b/src/level.h @@ -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 \ No newline at end of file diff --git a/src/mesh.h b/src/mesh.h index 1c36570..9f91174 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -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 \ No newline at end of file diff --git a/src/platform/android/app/src/main/cpp/main.cpp b/src/platform/android/app/src/main/cpp/main.cpp index 946b65d..a9fc827 100644 --- a/src/platform/android/app/src/main/cpp/main.cpp +++ b/src/platform/android/app/src/main/cpp/main.cpp @@ -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, diff --git a/src/platform/nix/main.cpp b/src/platform/nix/main.cpp index 1d686f4..9675f7a 100644 --- a/src/platform/nix/main.cpp +++ b/src/platform/nix/main.cpp @@ -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, diff --git a/src/platform/osx/main.cpp b/src/platform/osx/main.cpp index 535bb4d..bbcad13 100644 --- a/src/platform/osx/main.cpp +++ b/src/platform/osx/main.cpp @@ -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 diff --git a/src/platform/web/index.html b/src/platform/web/index.html index 1856ec9..1a80360 100644 --- a/src/platform/web/index.html +++ b/src/platform/web/index.html @@ -142,14 +142,14 @@ (.PHD, .PSX) -

- OpenLara on github & facebook
+

+ OpenLara on github & facebook

controls:
- 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)
+ keyboad: move - arrow keys, jump - Alt, action - Ctrl, draw weapon - Space, inventory - Tab, walk - Shift, side steps - ZX, change view - V)
gamepad: PSX controls for Xbox controller
- Change view: V
Time Control: R - slow motion, T - fast motion
FullScreen: Alt + Enter +

diff --git a/src/platform/web/main.cpp b/src/platform/web/main.cpp index c702218..8d880db 100644 --- a/src/platform/web/main.cpp +++ b/src/platform/web/main.cpp @@ -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', diff --git a/src/platform/win/OpenLara.vcxproj.filters b/src/platform/win/OpenLara.vcxproj.filters index 2c1879e..3226bd0 100644 --- a/src/platform/win/OpenLara.vcxproj.filters +++ b/src/platform/win/OpenLara.vcxproj.filters @@ -1,9 +1,13 @@  - - + + libs\minimp3 + + + libs\stb_vorbis + @@ -22,8 +26,6 @@ - - @@ -32,6 +34,12 @@ + + libs\minimp3 + + + libs\minimp3 + @@ -60,5 +68,14 @@ {3fcb6c00-268a-4570-b6ca-3bf1cf1e10d7} + + {0400c04a-7900-49a2-9b9d-6667d815168c} + + + {bc55e021-2a62-4bc1-8b80-0fd900f20b2b} + + + {f26df5a9-d32a-4e5e-948c-e632c42be9fa} + \ No newline at end of file diff --git a/src/platform/win/main.cpp b/src/platform/win/main.cpp index 43db6ee..f60d9e5 100644 --- a/src/platform/win/main.cpp +++ b/src/platform/win/main.cpp @@ -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', diff --git a/src/shader.h b/src/shader.h index 356e9a1..1882603 100644 --- a/src/shader.h +++ b/src/shader.h @@ -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 }; diff --git a/src/shaders/filter.glsl b/src/shaders/filter.glsl index d8a9aba..5b455d7 100644 --- a/src/shaders/filter.glsl +++ b/src/shaders/filter.glsl @@ -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 )====" \ No newline at end of file diff --git a/src/shaders/gui.glsl b/src/shaders/gui.glsl index 022f03e..4d157a3 100644 --- a/src/shaders/gui.glsl +++ b/src/shaders/gui.glsl @@ -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 )====" \ No newline at end of file diff --git a/src/ui.h b/src/ui.h index 4938797..c941a25 100644 --- a/src/ui.h +++ b/src/ui.h @@ -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); diff --git a/src/utils.h b/src/utils.h index b8241df..faf123e 100644 --- a/src/utils.h +++ b/src/utils.h @@ -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;