From 5a99d547bab3515e65f776329ebe83449153c93b Mon Sep 17 00:00:00 2001 From: XProger Date: Mon, 10 Oct 2016 02:43:20 +0300 Subject: [PATCH] #3 slide on slant blocks, climb on 2-3 clicks height blocks, asc/desc 1 click blocks while walk or run; #13 add dummy boulder trigger to stop inactive animation & sound, fix short angle checks and non-zero velocity for switch and keyhole triggers; --- bin/OpenLara.exe | Bin 82432 -> 86016 bytes src/camera.h | 24 ++-- src/controller.h | 60 ++++++++-- src/debug.h | 31 +++++ src/format.h | 21 +++- src/lara.h | 292 ++++++++++++++++++++++++++++++++++++++++------- src/level.h | 13 ++- src/shader.glsl | 2 +- src/sound.h | 6 +- src/trigger.h | 16 ++- src/utils.h | 21 +++- 11 files changed, 400 insertions(+), 86 deletions(-) diff --git a/bin/OpenLara.exe b/bin/OpenLara.exe index 1856fa2199c609752d82b4642c1a8ae0ca8558f7..979167775a0b9cce6db88fca11e3cf767552c3e8 100644 GIT binary patch delta 23961 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 HH~&8X?jLyZ delta 20266 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 diff --git a/src/camera.h b/src/camera.h index db648e3..97bf455 100644 --- a/src/camera.h +++ b/src/camera.h @@ -7,6 +7,8 @@ #define MAX_CLIP_PLANES 10 +#define CAMERA_OFFSET (1024.0f + 512.0f) + struct Frustum { struct Poly { @@ -64,15 +66,15 @@ struct Frustum { float t2 = v2.dot(plane.xyz) + plane.w; // hack for big float numbers - int s1 = sign((int)t1); - int s2 = sign((int)t2); + int s1 = (int)t1; + int s2 = (int)t2; if (s1 >= 0) { dst.vertices[dst.count++] = v1; ASSERT(dst.count < MAX_CLIP_PLANES); } - if (s1 * s2 < 0) { + if ((s1 ^ s2) < 0) { // check for opposite signs float k1 = t2 / (t2 - t1); float k2 = t1 / (t2 - t1); dst.vertices[dst.count++] = v1 * (float)k1 - v2 * (float)k2; @@ -224,24 +226,12 @@ struct Camera : Controller { Input::mouse.start.R = Input::mouse.pos; } - float height = 0.0f; - switch (owner->stand) { - case Controller::STAND_AIR : - case Controller::STAND_GROUND : - height = 768.0f; - break; - case Controller::STAND_UNDERWATER : - case Controller::STAND_ONWATER : - height = 256.0f; - break; - } - angle = owner->angle + angleAdv; angle.z = 0.0f; //angle.x = min(max(angle.x, -80 * DEG2RAD), 80 * DEG2RAD); vec3 dir; - target = vec3(owner->pos.x, owner->pos.y - height, owner->pos.z); + target = vec3(owner->pos.x, owner->pos.y, owner->pos.z) + owner->getViewOffset(); if (actCamera > -1) { TR::Camera &c = level->cameras[actCamera]; @@ -260,7 +250,7 @@ struct Camera : Controller { dir = getDir(); if (owner->state != Lara::STATE_BACK_JUMP || actTargetEntity > -1) { - vec3 eye = target - dir * 1024.0f; + vec3 eye = target - dir * CAMERA_OFFSET; destPos = trace(owner->getRoomIndex(), target, eye); lastDest = destPos; } else { diff --git a/src/controller.h b/src/controller.h index 38c5959..b84eff5 100644 --- a/src/controller.h +++ b/src/controller.h @@ -6,12 +6,14 @@ #define GRAVITY 6.0f #define NO_OVERLAP 0x7FFFFFFF +#define SPRITE_FPS 10.0f + struct Controller { TR::Level *level; int entity; enum Stand { - STAND_AIR, STAND_GROUND, STAND_UNDERWATER, STAND_ONWATER + STAND_AIR, STAND_GROUND, STAND_SLIDE, STAND_HANG, STAND_UNDERWATER, STAND_ONWATER } stand; int state; int mask; @@ -54,7 +56,7 @@ struct Controller { pos = vec3((float)e.x, (float)e.y, (float)e.z); angle = vec3(0.0f, e.rotation, 0.0f); stand = STAND_GROUND; - animIndex = e.modelIndex > 0 ? level->models[e.modelIndex - 1].animation : 0; + animIndex = e.modelIndex > 0 ? getModel().animation : 0; state = level->anims[animIndex].state; } @@ -63,6 +65,8 @@ struct Controller { e.x = int(pos.x); e.y = int(pos.y); e.z = int(pos.z); + while (angle.y < 0.0f) angle.y += 2 * PI; + while (angle.y > 2 * PI) angle.y -= 2 * PI; e.rotation = angle.y; } @@ -76,6 +80,10 @@ struct Controller { pos.z >= min.z && pos.z <= max.z; } + TR::Model& getModel() const { + return level->models[getEntity().modelIndex - 1]; + } + TR::Entity& getEntity() const { return level->entities[entity]; } @@ -198,7 +206,7 @@ struct Controller { if (info.roomNext != 0xFF) entity.room = info.roomNext; - if (entity.y >= info.floor) { + if (entity.y > info.floor) { if (info.roomBelow == 0xFF) { entity.y = info.floor; pos.y = entity.y; @@ -275,6 +283,8 @@ struct Controller { virtual int getHeight() { return 0; } virtual int getStateAir() { return state; } virtual int getStateGround() { return state; } + virtual int getStateSlide() { return state; } + virtual int getStateHang() { return state; } virtual int getStateUnderwater() { return state; } virtual int getStateOnwater() { return state; } virtual int getStateDeath() { return state; } @@ -290,6 +300,10 @@ struct Controller { state = getStateDeath(); else if (stand == STAND_GROUND) state = getStateGround(); + else if (stand == STAND_SLIDE) + state = getStateSlide(); + else if (stand == STAND_HANG) + state = getStateHang(); else if (stand == STAND_AIR) state = getStateAir(); else if (stand == STAND_UNDERWATER) @@ -317,10 +331,34 @@ struct Controller { virtual void updateState() {} + virtual vec3 getAnimMove() { + TR::Animation *anim = &level->anims[animIndex]; + int16 *ptr = &level->commands[anim->animCommand]; + + for (int i = 0; i < anim->acCount; i++) { + int cmd = *ptr++; + switch (cmd) { + case TR::ANIM_CMD_MOVE : { // cmd position + int16 sx = *ptr++; + int16 sy = *ptr++; + int16 sz = *ptr++; + return vec3((float)sx, (float)sy, (float)sz); + break; + } + case TR::ANIM_CMD_SPEED : // cmd jump speed + case TR::ANIM_CMD_SOUND : // play sound + case TR::ANIM_CMD_SPECIAL : // special commands + ptr += 2; + break; + } + } + return vec3(0.0f); + } + virtual void updateAnimation(bool commands) { int frameIndex = int((animTime += Core::deltaTime) * 30.0f); TR::Animation *anim = &level->anims[animIndex]; - bool endFrame = frameIndex > anim->frameEnd - anim->frameStart; + bool endFrame = frameIndex > anim->frameEnd - anim->frameStart; // apply animation commands if (commands) { @@ -334,7 +372,7 @@ struct Controller { int16 sy = *ptr++; int16 sz = *ptr++; if (endFrame) { - pos = pos + vec3(sx, sy, sz).rotateY(angle.y); + pos = pos + vec3(sx, sy, sz).rotateY(-angle.y); updateEntity(); LOG("move: %d %d %d\n", (int)sx, (int)sy, (int)sz); } @@ -360,11 +398,9 @@ struct Controller { int frame = (*ptr++); int id = (*ptr++) & 0x3FFF; int idx = frame - anim->frameStart; - - if (idx > animPrevFrame && idx <= frameIndex) { - if (getEntity().id != TR::Entity::ENEMY_BAT) // temporary mute the bat - playSound(id); - } + + if (idx > animPrevFrame && idx <= frameIndex) + playSound(id); break; } case TR::ANIM_CMD_SPECIAL : // special commands @@ -430,14 +466,14 @@ struct SpriteController : Controller { animTime += Core::deltaTime; if (animated) { - frame = int(animTime * 10.0f); + frame = int(animTime * SPRITE_FPS); TR::SpriteSequence &seq = getSequence(); if (instant && frame >= seq.sCount) remove = true; else frame %= seq.sCount; } else - if (instant && animTime >= 0.1f) + if (instant && animTime >= (1.0f / SPRITE_FPS)) remove = true; if (remove) { diff --git a/src/debug.h b/src/debug.h index 5996322..98c3261 100644 --- a/src/debug.h +++ b/src/debug.h @@ -138,6 +138,14 @@ namespace Debug { glEnd(); } + void line(const vec3 &a, const vec3 &b, const vec4 &color) { + glBegin(GL_LINES); + glColor4fv((GLfloat*)&color); + glVertex3fv((GLfloat*)&a); + glVertex3fv((GLfloat*)&b); + glEnd(); + } + void text(const vec2 &pos, const vec4 &color, const char *str) { glMatrixMode(GL_MODELVIEW); glPushMatrix(); @@ -189,6 +197,8 @@ namespace Debug { bool isPortal = false; + float fx = 0.0f, fz = 0.0f; + TR::FloorData *fd = &level.floors[floorIndex]; TR::FloorData::Command cmd; do { @@ -208,6 +218,9 @@ namespace Debug { auto &p = cmd.func == 0x02 ? vf : vc; if (cmd.func == TR::FloorData::FLOOR) { // floor + fx = (float)slant.x; + fz = (float)slant.z; + if (sx > 0) { p[0].y += sx; p[3].y += sx; @@ -283,6 +296,24 @@ namespace Debug { glVertex3f(x, c.y, z); glEnd(); } + + vec3 a = (vf[0] + vf[1] + vf[2] + vf[3]) * 0.25f; + vec3 n = vec3(-fx, -4.0f, -fz).normal(); + vec3 b = a + n * 1.0f; + + vec3 v = ((Controller*)level.entities[0].controller)->getDir(); + vec3 p = v - n * n.dot(v); + vec3 c = b + p.normal() * 256.0f; + + glBegin(GL_LINES); + glColor3f(1, 1, 1); + glVertex3fv((GLfloat*)&a); + glVertex3fv((GLfloat*)&b); + + glColor3f(1, 1, 0); + glVertex3fv((GLfloat*)&b); + glVertex3fv((GLfloat*)&c); + glEnd(); } void debugBox(const TR::Box &b) { diff --git a/src/format.h b/src/format.h index 0677c6d..2aa3284 100644 --- a/src/format.h +++ b/src/format.h @@ -33,6 +33,7 @@ namespace TR { enum { SND_NO = 2, + SND_LANDING = 4, SND_BUBBLE = 37, SND_SECRET = 173, }; @@ -280,7 +281,7 @@ namespace TR { TRAP_FLOOR = 35, TRAP_BLADE = 36, TRAP_SPIKES = 37, - TRAP_STONE = 38, + TRAP_BOULDER = 38, TRAP_DART = 39, TRAP_DARTGUN = 40, @@ -633,6 +634,7 @@ namespace TR { struct FloorInfo { int floor, ceiling; + int slantX, slantZ; int roomNext, roomBelow, roomAbove; int floorIndex; int kill; @@ -640,6 +642,19 @@ namespace TR { Trigger trigger; FloorData::TriggerInfo trigInfo; FloorData::TriggerCommand trigCmd[16]; + + vec3 getNormal() { + return vec3((float)-slantX, -4.0f, (float)-slantZ).normal(); + } + + vec3 getSlant(const vec3 &dir) { + // project floor normal into plane(dir, up) + vec3 r = vec3(dir.z, 0.0f, -dir.x); // up(0, 1, 0).cross(dir) + vec3 n = getNormal(); + n = n - r * r.dot(n); + // project dir into plane(dir, n) + return n.cross(dir.cross(n)).normal(); + } }; bool secrets[MAX_SECRETS_COUNT]; @@ -891,6 +906,8 @@ namespace TR { info.floor = 256 * (int)s.floor; info.ceiling = 256 * (int)s.ceiling; + info.slantX = 0; + info.slantZ = 0; info.roomNext = 255; info.roomBelow = s.roomBelow; info.roomAbove = s.roomAbove; @@ -919,6 +936,8 @@ namespace TR { int sx = (int)slant.x; int sz = (int)slant.z; if (cmd.func == FloorData::FLOOR) { + info.slantX = sx; + info.slantZ = sz; info.floor -= sx * (sx > 0 ? (dx - 1024) : dx) >> 2; info.floor -= sz * (sz > 0 ? (dz - 1024) : dz) >> 2; } else { diff --git a/src/lara.h b/src/lara.h index f3a5a0e..0026516 100644 --- a/src/lara.h +++ b/src/lara.h @@ -17,6 +17,8 @@ #define MAX_TRIGGER_ACTIONS 64 +#define DESCENT_SPEED 2048.0f + struct Lara : Controller { ActionCommand actionList[MAX_TRIGGER_ACTIONS]; @@ -24,19 +26,39 @@ struct Lara : Controller { // http://www.tombraiderforums.com/showthread.php?t=148859 enum { ANIM_STAND = 11, + + ANIM_CLIMB_JUMP = 26, + ANIM_FALL = 34, ANIM_SMASH_JUMP = 32, + + ANIM_CLIMB_3 = 42, + + ANIM_CLIMB_2 = 50, + ANIM_SMASH_RUN_LEFT = 53, ANIM_SMASH_RUN_RIGHT = 54, + ANIM_RUN_ASCEND_LEFT = 55, + ANIM_RUN_ASCEND_RIGHT = 56, + ANIM_WALK_ASCEND_LEFT = 57, + ANIM_WALK_ASCEND_RIGHT = 58, + ANIM_WALK_DESCEND_RIGHT = 59, + ANIM_WALK_DESCEND_LEFT = 60, + ANIM_BACK_DESCEND_LEFT = 61, + ANIM_BACK_DESCEND_RIGHT = 62, + + ANIM_SLIDE_FORTH = 70, + ANIM_SLIDE_BACK = 105, + ANIM_WATER_FALL = 112, ANIM_TO_ONWATER = 114, - ANIM_STAND_ROLL_BEGIN = 146, - ANIM_STAND_ROLL_END = 147, ANIM_TO_UNDERWATER = 119, ANIM_HIT_FRONT = 125, ANIM_HIT_BACK = 126, ANIM_HIT_LEFT = 127, ANIM_HIT_RIGHT = 128, + ANIM_STAND_ROLL_BEGIN = 146, + ANIM_STAND_ROLL_END = 147, }; // http://www.tombraiderforums.com/showthread.php?t=211681 @@ -60,7 +82,7 @@ struct Lara : Controller { STATE_BACK, STATE_SWIM, STATE_GLIDE, - STATE_NULL_19, + STATE_HANG_JUMP, STATE_FAST_TURN, STATE_STEP_RIGHT, STATE_STEP_LEFT, @@ -127,10 +149,6 @@ struct Lara : Controller { // updateEntity(); } - bool isMovingState(int state) { - return state == STATE_RUN || state == STATE_FAST_BACK || state == STATE_ROLL || state == STATE_WALK || state == STATE_STEP_LEFT || state == STATE_STEP_RIGHT; - } - bool waterOut(int &outState) { // TODO: playSound 36 vec3 dst = pos + getDir() * 32.0f; @@ -181,14 +199,14 @@ struct Lara : Controller { actionState = (isActive && stand == STAND_GROUND) ? STATE_SWITCH_UP : STATE_SWITCH_DOWN; if ((mask & ACTION) == 0 || state == actionState) return; - if (fabsf(level->entities[info.trigCmd[0].args].rotation - e.rotation) > PI * 0.25f) + if (fabsf(shortAngle(level->entities[info.trigCmd[0].args].rotation, e.rotation)) > PI * 0.25f) // TODO clamp angles return; break; case TR::Level::Trigger::KEY : actionState = STATE_USE_KEY; if (isActive || (mask & ACTION) == 0 || state == actionState) // TODO: STATE_USE_PUZZLE return; - if (fabsf(level->entities[info.trigCmd[0].args].rotation - e.rotation) > PI * 0.25f) + if (fabsf(shortAngle(level->entities[info.trigCmd[0].args].rotation, e.rotation)) > PI * 0.25f) // TODO clamp angles return; break; case TR::Level::Trigger::PICKUP : @@ -208,6 +226,7 @@ struct Lara : Controller { angle.y = p.rotation; angle.x = 0; pos = vec3(p.x, p.y, p.z) + vec3(sinf(angle.y), 0, cosf(angle.y)) * (stand == STAND_GROUND ? 384 : 128); + velocity = vec3(0.0f); updateEntity(); } @@ -245,6 +264,70 @@ struct Lara : Controller { activateNext(); } + vec3 getViewOffset() { + TR::Animation *anim = &level->anims[animIndex]; + TR::Model &model = getModel(); + + float k = animTime * 30.0f / anim->frameRate; + int fIndex = (int)k; + int fCount = (anim->frameEnd - anim->frameStart) / anim->frameRate + 1; + + int fSize = sizeof(TR::AnimFrame) + model.mCount * sizeof(uint16) * 2; + k = k - fIndex; + + int fIndexA = fIndex % fCount, fIndexB = (fIndex + 1) % fCount; + TR::AnimFrame *frameA = (TR::AnimFrame*)&level->frameData[(anim->frameOffset + fIndexA * fSize) >> 1]; + + TR::Animation *nextAnim = NULL; + + vec3 move(0.0f); + if (fIndexB == 0) { + move = getAnimMove(); + nextAnim = &level->anims[anim->nextAnimation]; + fIndexB = (anim->nextFrame - nextAnim->frameStart) / nextAnim->frameRate; + } else + nextAnim = anim; + + TR::AnimFrame *frameB = (TR::AnimFrame*)&level->frameData[(nextAnim->frameOffset + fIndexB * fSize) >> 1]; + + float h = ((vec3)frameA->pos).lerp(move + frameB->pos, k).y; + + switch (stand) { + case Controller::STAND_AIR : + case Controller::STAND_GROUND : + case Controller::STAND_SLIDE : + case Controller::STAND_HANG : + h -= 256.0f; + break; + case Controller::STAND_UNDERWATER : + case Controller::STAND_ONWATER : + h -= 128.0f; + break; + } + + + return vec3(0.0f, h, 0.0f); + +/* + + + return offset; + */ + /* + switch (stand) { + case Controller::STAND_AIR : + case Controller::STAND_GROUND : + case Controller::STAND_SLIDE : + case Controller::STAND_HANG : + offset.y = 768.0f; + break; + case Controller::STAND_UNDERWATER : + case Controller::STAND_ONWATER : + offset.y = 256.0f; + break; + }*/ + } + virtual Stand getStand() { if (stand == STAND_ONWATER && state != STATE_DIVE && state != STATE_STOP) return stand; @@ -252,11 +335,20 @@ struct Lara : Controller { if (getRoom().flags & TR::ROOM_FLAG_WATER) return STAND_UNDERWATER; // TODO: ONWATER - int extra = isMovingState(state) ? 256 : 0; - TR::Entity &e = getEntity(); TR::Level::FloorInfo info; level->getFloorInfo(e.room, e.x, e.z, info); + + if (stand == STAND_SLIDE || (stand == STAND_AIR && velocity.y > 0) || stand == STAND_GROUND) { + if (e.y + 8 >= info.floor && (abs(info.slantX) > 2 || abs(info.slantZ) > 2)) { + if (stand == STAND_AIR) + playSound(TR::SND_LANDING); + return STAND_SLIDE; + } + } + + int extra = stand != STAND_AIR ? 256 : 0; + if (info.roomBelow == 0xFF && e.y + extra >= info.floor) return STAND_GROUND; @@ -283,7 +375,8 @@ struct Lara : Controller { } virtual int getStateGround() { - angle.x = 0.0f; + angle.x = 0.0f; + /* // hit test if (animIndex != ANIM_HIT_FRONT) @@ -314,7 +407,11 @@ struct Lara : Controller { if (mask & JUMP) { if ((mask & FORTH) && state == STATE_FORWARD_JUMP) return STATE_RUN; - return state == STATE_RUN ? STATE_FORWARD_JUMP : STATE_COMPRESS; + if (state == STATE_RUN) + return STATE_FORWARD_JUMP; + if (animIndex == ANIM_SLIDE_BACK) + return STATE_SLIDE_BACK; + return STATE_COMPRESS; } // walk button is pressed @@ -324,14 +421,69 @@ struct Lara : Controller { if (mask & LEFT) return STATE_STEP_LEFT; if (mask & RIGHT) return STATE_STEP_RIGHT; return STATE_STOP; - } - - // only dpad buttons pressed - if (mask & FORTH) return STATE_RUN; - if (mask & BACK) return STATE_FAST_BACK; - if (mask & LEFT) return turnTime < FAST_TURN_TIME ? STATE_TURN_LEFT : STATE_FAST_TURN; - if (mask & RIGHT) return turnTime < FAST_TURN_TIME ? STATE_TURN_RIGHT : STATE_FAST_TURN; - return STATE_STOP; + } + + if ( (mask & (FORTH | ACTION)) == (FORTH | ACTION) ) { + vec3 p = pos + getDir() * 64.0f; + TR::Level::FloorInfo info; + level->getFloorInfo(getRoomIndex(), (int)p.x, (int)p.z, info); + int h = (int)pos.y - info.floor; + if (h >= 2 * 256 - 16 && h <= 2 * 256 + 16 && animIndex != ANIM_CLIMB_2) + return setAnimation(ANIM_CLIMB_2); + if (h >= 3 * 256 - 16 && h <= 3 * 256 + 16 && animIndex != ANIM_CLIMB_3) + return setAnimation(ANIM_CLIMB_3); + if (h >= 4 * 256 - 16 && h <= 7 * 256 + 16 && state != STATE_HANG_JUMP) + return setAnimation(ANIM_CLIMB_JUMP); + } + + // only dpad buttons pressed + if (mask & FORTH) return STATE_RUN; + if (mask & BACK) return STATE_FAST_BACK; + if (mask & LEFT) return turnTime < FAST_TURN_TIME ? STATE_TURN_LEFT : STATE_FAST_TURN; + if (mask & RIGHT) return turnTime < FAST_TURN_TIME ? STATE_TURN_RIGHT : STATE_FAST_TURN; + return STATE_STOP; + } + + virtual int getStateSlide() { + + TR::Entity &e = getEntity(); + + if (state != STATE_SLIDE && state != STATE_SLIDE_BACK) { + TR::Level::FloorInfo info; + level->getFloorInfo(e.room, e.x, e.z, info); + + int sx = abs(info.slantX), sz = abs(info.slantZ); + // get direction + float dir; + if (sx > sz) + dir = info.slantX > 0 ? 3.0f : 1.0f; + else + dir = info.slantZ > 0 ? 2.0f : 0.0f; + dir *= PI * 0.5f; + + int aIndex = ANIM_SLIDE_FORTH; + if (fabsf(shortAngle(dir, angle.y)) > PI * 0.5f) { + aIndex = ANIM_SLIDE_BACK; + dir += PI; + } + + angle.y = dir; + updateEntity(); + return setAnimation(aIndex); + } + + if (mask & JUMP) { + stand = STAND_GROUND; + pos.y -= 16; + updateEntity(); + return state == STATE_SLIDE ? STATE_FORWARD_JUMP : STATE_BACK_JUMP; + } + + return state; + } + + virtual int getStateHang() { + return Controller::getStateHang(); } virtual int getStateUnderwater() { @@ -417,11 +569,13 @@ struct Lara : Controller { if (Input::down[ikEnter]) { if (!lState) { lState = true; + /* static int snd_id = 0;//160; playSound(snd_id); + */ // setAnimation(snd_id); - LOG("sound: %d\n", snd_id++); - /* + //LOG("sound: %d\n", snd_id++); + LOG("state: %d\n", anim->state); for (int i = 0; i < anim->scCount; i++) { auto &sc = level->states[anim->scOffset + i]; @@ -432,7 +586,7 @@ struct Lara : Controller { } LOG("\n"); } - */ + } } else @@ -486,6 +640,7 @@ struct Lara : Controller { case STATE_SURF_BACK : case STATE_BACK_JUMP : case STATE_FAST_BACK : + case STATE_SLIDE_BACK : angleExt += PI; break; case STATE_LEFT_JUMP : @@ -514,7 +669,9 @@ struct Lara : Controller { case STAND_AIR : velocity.y += GRAVITY * dt; break; - case STAND_GROUND : + case STAND_GROUND : + case STAND_SLIDE : + case STAND_HANG : case STAND_ONWATER : { float speed = 0.0f; @@ -531,12 +688,18 @@ struct Lara : Controller { speed = -speed; } - velocity.x = sinf(angleExt) * speed; - velocity.z = cosf(angleExt) * speed; - if (stand == STAND_GROUND) - velocity.y += GRAVITY * dt; - else + if (stand == STAND_ONWATER) { + velocity.x = sinf(angleExt) * speed; + velocity.z = cosf(angleExt) * speed; velocity.y = 0.0f; + } else { + TR::Entity &e = getEntity(); + TR::Level::FloorInfo info; + level->getFloorInfo(e.room, e.x, e.z, info); + + vec3 v(sinf(angleExt), 0.0f, cosf(angleExt)); + velocity = info.getSlant(v) * speed; + } break; } case STAND_UNDERWATER : { @@ -563,7 +726,7 @@ struct Lara : Controller { vec3 p = pos; pos = pos + offset; - + TR::Level::FloorInfo info; level->getFloorInfo(getEntity().room, (int)pos.x, (int)pos.z, info); @@ -573,6 +736,8 @@ struct Lara : Controller { int height = getHeight(); bool canPassGap; + float h = info.floor - pos.y; + switch (stand) { case STAND_AIR : canPassGap = ((int)p.y - d) <= 512 && (info.roomAbove != 0xFF || (pos.y - height - info.ceiling > -256)); @@ -585,12 +750,19 @@ struct Lara : Controller { break; } default : // TODO: height - if (state == STATE_WALK || state == STATE_BACK || state == STATE_STEP_LEFT || state == STATE_STEP_RIGHT) - canPassGap = delta <= 256; + if (state == STATE_WALK || state == STATE_BACK) + canPassGap = h >= -256 && h <= 256; else - canPassGap = delta >= -256 - 16; + if (state == STATE_STEP_LEFT || state == STATE_STEP_RIGHT) + canPassGap = h >= -128 && h <= 128; + else + canPassGap = h >= -256 - 16; } + TR::Animation *anim = &level->anims[animIndex]; + int frame = int(animTime * 30.0f); + bool left = (anim->frameEnd - anim->frameStart) / 2 > frame; + if (d == NO_OVERLAP || !canPassGap) { pos = p; // TODO: use smart ejection @@ -602,22 +774,56 @@ struct Lara : Controller { velocity.x = -velocity.x * 0.5f; velocity.z = -velocity.z * 0.5f; velocity.y = 0.0f; - } else + } else { velocity.x = velocity.z = 0.0f; + pos.y = p.y; + updateEntity(); + } break; case STAND_GROUND : - if (delta <= -256 * 4 && state == STATE_RUN) - setAnimation(ANIM_SMASH_RUN_LEFT); // TODO: RIGHT - else - setAnimation(ANIM_STAND); + if (state != STATE_UP_JUMP) { // early stage of up jump + if (delta <= -256 * 4 && state == STATE_RUN) + setAnimation(left ? ANIM_SMASH_RUN_LEFT : ANIM_SMASH_RUN_RIGHT); + else + setAnimation(ANIM_STAND); + } else { + pos.y = p.y; + updateEntity(); + } velocity.x = velocity.z = 0.0f; break; - case STAND_UNDERWATER : - case STAND_ONWATER : - break; + default : ;// no smash animation } - } else + } else { + if (state == STATE_RUN || state == STATE_WALK || state == STATE_BACK || state == STATE_ROLL) { + if (h <= -128 && h >= -256) { // ascend + if (state == STATE_RUN) setAnimation(left ? ANIM_RUN_ASCEND_LEFT : ANIM_RUN_ASCEND_RIGHT); + if (state == STATE_WALK) setAnimation(left ? ANIM_WALK_ASCEND_LEFT : ANIM_WALK_ASCEND_RIGHT); + pos.y = info.floor; + } + + if (h >= 128 && h <= 256 && (state == STATE_WALK || state == STATE_BACK)) { // descend + if (state == STATE_WALK) setAnimation(left ? ANIM_WALK_DESCEND_LEFT : ANIM_WALK_DESCEND_RIGHT); + if (state == STATE_BACK) setAnimation(left ? ANIM_BACK_DESCEND_LEFT : ANIM_BACK_DESCEND_RIGHT); + pos.y = info.floor; + } + + if (h > 0 && (state == STATE_RUN || state == STATE_ROLL)) { + pos.y += DESCENT_SPEED * Core::deltaTime; + } + } + + if (state == STATE_FAST_BACK) { + if (h >= 255) { + stand = STAND_AIR; + setAnimation(ANIM_FALL); + } else + pos.y += DESCENT_SPEED * Core::deltaTime; + } + + updateEntity(); + } } }; diff --git a/src/level.h b/src/level.h index f85ccf1..9479090 100644 --- a/src/level.h +++ b/src/level.h @@ -86,9 +86,11 @@ struct Level { case TR::Entity::TRAP_FLOOR : case TR::Entity::TRAP_BLADE : case TR::Entity::TRAP_SPIKES : - case TR::Entity::TRAP_STONE : entity.controller = new Trigger(&level, i, true); break; + case TR::Entity::TRAP_BOULDER : + entity.controller = new Boulder(&level, i); + break; case TR::Entity::TRAP_DARTGUN : entity.controller = new Dartgun(&level, i); break; @@ -386,12 +388,15 @@ struct Level { TR::Animation *nextAnim = NULL; + vec3 move(0.0f); if (fIndexB == 0) { + if (controller) + move = controller->getAnimMove(); nextAnim = &level.anims[anim->nextAnimation]; fIndexB = (anim->nextFrame - nextAnim->frameStart) / nextAnim->frameRate; } else nextAnim = anim; - + TR::AnimFrame *frameB = (TR::AnimFrame*)&level.frameData[(nextAnim->frameOffset + fIndexB * fSize) >> 1]; vec3 bmin = frameA->box.min().lerp(frameB->box.min(), k); @@ -403,7 +408,7 @@ struct Level { mat4 m; m.identity(); - m.translate(((vec3)frameA->pos).lerp(frameB->pos, k)); + m.translate(((vec3)frameA->pos).lerp(move + frameB->pos, k)); int sIndex = 0; mat4 stack[20]; @@ -601,7 +606,7 @@ struct Level { // Debug::Level::lights(level); // Debug::Level::portals(level); // Debug::Level::meshes(level); - Debug::Level::entities(level); + // Debug::Level::entities(level); Debug::Level::info(level, lara->getEntity()); Debug::end(); #endif diff --git a/src/shader.glsl b/src/shader.glsl index b3bb389..82e4d54 100644 --- a/src/shader.glsl +++ b/src/shader.glsl @@ -88,7 +88,7 @@ varying vec4 vColor; // fog float fog = clamp(1.0 / exp(gl_FragCoord.z / gl_FragCoord.w * 0.000025), 0.0, 1.0); - gl_FragColor = mix(vec4(0.0), color, fog); + gl_FragColor = mix(vec4(0.0, 0.0, 0.0, 1.0), color, fog); } #endif )====" \ No newline at end of file diff --git a/src/sound.h b/src/sound.h index f0daf42..8517163 100644 --- a/src/sound.h +++ b/src/sound.h @@ -380,8 +380,10 @@ namespace Sound { if (!stream) return; if (channelsCount < SND_CHANNELS_MAX) channels[channelsCount++] = new Sample(stream, volume, pitch, flags); - else - LOG("! no free channels\n"); + else { + LOG("! no free channels\n"); + delete stream; + } } } diff --git a/src/trigger.h b/src/trigger.h index ebf7f6a..458419f 100644 --- a/src/trigger.h +++ b/src/trigger.h @@ -109,8 +109,10 @@ struct Dartgun : Trigger { level->entities[dartIndex].controller = new Dart(level, dartIndex); int smokeIndex = level->entityAdd(TR::Entity::SMOKE, entity.room, (int)pos.x, (int)pos.y, (int)pos.z, entity.rotation, -1); - if (smokeIndex > -1) + if (smokeIndex > -1) { + level->entities[smokeIndex].intensity = 0x1FFF - level->rooms[entity.room].ambient; level->entities[smokeIndex].controller = new SpriteController(level, smokeIndex); + } playSound(151); @@ -119,4 +121,16 @@ struct Dartgun : Trigger { }; +struct Boulder : Trigger { + + Boulder(TR::Level *level, int entity) : Trigger(level, entity, true) {} + + virtual void update() { + if (getEntity().flags & ENTITY_FLAG_ACTIVE) { + updateAnimation(true); + updateEntity(); + } + } +}; + #endif \ No newline at end of file diff --git a/src/utils.h b/src/utils.h index d411c7c..f45f857 100644 --- a/src/utils.h +++ b/src/utils.h @@ -25,6 +25,7 @@ #define PI 3.14159265358979323846f +#define PI2 (PI * 2.0f) #define DEG2RAD (PI / 180.0f) #define RAD2DEG (180.0f / PI) #define EPS FLT_EPSILON @@ -74,6 +75,16 @@ inline const int sign(const T &x) { return x > 0 ? 1 : (x < 0 ? -1 : 0); } +float clampAngle(float a) { + return a < -PI ? a + PI2 : (a >= PI ? a - PI2 : a); +} + +float shortAngle(float a, float b) { + float n = clampAngle(b) - clampAngle(a); + return clampAngle(n - int(n / PI2) * PI2); +} + + struct vec2 { float x, y; vec2() {} @@ -110,7 +121,7 @@ struct vec3 { vec3 cross(const vec3 &v) const { return vec3(y*v.z - z*v.y, z*v.x - x*v.z, x*v.y - y*v.x); } float length2() const { return dot(*this); } float length() const { return sqrtf(length2()); } - vec3 normal() const { float s = length(); return s == 0.0 ? (*this) : (*this)*(1.0f/s); } + vec3 normal() const { float s = length(); return s == 0.0f ? (*this) : (*this)*(1.0f/s); } vec3 lerp(const vec3 &v, const float t) const { if (t <= 0.0f) return *this; @@ -227,10 +238,10 @@ struct quat { temp = q; if (1.0f - cosom > EPS) { - omega = acos(cosom); - sinom = 1.0f / sin(omega); - scale0 = sin((1.0f - t) * omega) * sinom; - scale1 = sin(t * omega) * sinom; + omega = acosf(cosom); + sinom = 1.0f / sinf(omega); + scale0 = sinf((1.0f - t) * omega) * sinom; + scale1 = sinf(t * omega) * sinom; } else { scale0 = 1.0f - t; scale1 = t;