mirror of
https://github.com/XProger/OpenLara.git
synced 2025-04-22 03:51:58 +02:00
This commit is contained in:
parent
65b73febbc
commit
bbf6b8a3c5
28
src/cache.h
28
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
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
66
src/core.h
66
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)
|
||||
|
12
src/debug.h
12
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
113
src/format.h
113
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) {
|
||||
|
20
src/game.h
20
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();
|
||||
}
|
||||
}
|
||||
|
210
src/input.h
210
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
541
src/inventory.h
541
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
|
91
src/lara.h
91
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;
|
||||
|
324
src/level.h
324
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
|
149
src/mesh.h
149
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
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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',
|
||||
|
@ -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>
|
@ -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',
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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
|
||||
)===="
|
@ -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
207
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);
|
||||
|
31
src/utils.h
31
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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user