diff --git a/bin/OpenLara.exe b/bin/OpenLara.exe index c350d1d..de8cf3b 100644 Binary files a/bin/OpenLara.exe and b/bin/OpenLara.exe differ diff --git a/src/camera.h b/src/camera.h index f66efd7..22c96a8 100644 --- a/src/camera.h +++ b/src/camera.h @@ -19,10 +19,9 @@ struct Camera : Controller { float timer; int actTargetEntity, actCamera; - vec3 viewOffset; Camera(TR::Level *level, Lara *owner) : Controller(level, owner ? owner->entity : 0), owner(owner), frustum(new Frustum()), timer(0.0f), actTargetEntity(-1), actCamera(-1) { - fov = 75.0f; + fov = 80.0f; znear = 128; zfar = 100.0f * 1024.0f; angleAdv = vec3(0.0f); @@ -30,8 +29,8 @@ struct Camera : Controller { if (owner) { room = owner->getEntity().room; pos = pos - owner->getDir() * 1024.0f; + target = owner->getViewPoint(); } - viewOffset = owner->getViewOffset(); } virtual ~Camera() { @@ -57,6 +56,8 @@ struct Camera : Controller { } virtual void update() { + actTargetEntity = owner->target; + if (timer > 0.0f) { timer -= Core::deltaTime; if (timer <= 0.0f) { @@ -90,11 +91,9 @@ struct Camera : Controller { angle.z = 0.0f; //angle.x = min(max(angle.x, -80 * DEG2RAD), 80 * DEG2RAD); - float lerpFactor = (actTargetEntity == -1) ? 4.0f : 10.0f; - viewOffset = viewOffset.lerp(owner->getViewOffset(), Core::deltaTime * lerpFactor); - + float lerpFactor = (actTargetEntity == -1) ? 6.0f : 10.0f; vec3 dir; - target = vec3(owner->pos.x, owner->pos.y, owner->pos.z) + viewOffset; + target = target.lerp(owner->getViewPoint(), lerpFactor * Core::deltaTime); if (actCamera > -1) { TR::Camera &c = level->cameras[actCamera]; diff --git a/src/controller.h b/src/controller.h index 02aac82..0834013 100644 --- a/src/controller.h +++ b/src/controller.h @@ -39,10 +39,6 @@ struct Controller { float angleExt; - int health; - - float turnTime; - int *meshes; int mCount; quat *animOverrides; // left & right arms animation frames @@ -59,7 +55,7 @@ struct Controller { ActionCommand(TR::Action action, int value, float timer, ActionCommand *next = NULL) : action(action), value(value), timer(timer), next(next) {} } *actionCommand; - Controller(TR::Level *level, int entity) : level(level), entity(entity), velocity(0.0f), animTime(0.0f), animPrevFrame(0), health(100), turnTime(0.0f), actionCommand(NULL), mCount(0), meshes(NULL), animOverrides(NULL), animOverrideMask(0), joints(NULL) { + Controller(TR::Level *level, int entity) : level(level), entity(entity), velocity(0.0f), animTime(0.0f), animPrevFrame(0), actionCommand(NULL), mCount(0), meshes(NULL), animOverrides(NULL), animOverrideMask(0), joints(NULL) { TR::Entity &e = getEntity(); pos = vec3((float)e.x, (float)e.y, (float)e.z); angle = vec3(0.0f, e.rotation, 0.0f); @@ -96,6 +92,89 @@ struct Controller { } } + int getFrameIndex(int animIndex, float t) { + TR::Animation &anim = level->anims[animIndex]; + return int(t * 30.0f / anim.frameRate) % ((anim.frameEnd - anim.frameStart) / anim.frameRate + 1); + } + + void getFrames(TR::AnimFrame **frameA, TR::AnimFrame **frameB, float &t, int animIndex, float animTime, bool nextAnim = false, vec3 *move = NULL) { + TR::Animation *anim = &level->anims[animIndex]; + + t = animTime * 30.0f / anim->frameRate; + int fIndex = (int)t; + int fCount = (anim->frameEnd - anim->frameStart) / anim->frameRate + 1; + + int fSize = sizeof(TR::AnimFrame) + getModel().mCount * sizeof(uint16) * 2; + t -= fIndex; + + int fIndexA = fIndex % fCount, fIndexB = (fIndex + 1) % fCount; + *frameA = (TR::AnimFrame*)&level->frameData[(anim->frameOffset + fIndexA * fSize) >> 1]; + + if (!fIndexB) { + if (move) + *move = getAnimMove(); + if (nextAnim) { + int nextFrame = anim->nextFrame; + anim = &level->anims[anim->nextAnimation]; + fIndexB = (nextFrame - anim->frameStart) / anim->frameRate; + } + } + *frameB = (TR::AnimFrame*)&level->frameData[(anim->frameOffset + fIndexB * fSize) >> 1]; + } + + mat4 getJoint(int index, bool postRot = false) { + mat4 matrix; + matrix.identity(); + + matrix.translate(pos); + if (angle.y != 0.0f) matrix.rotateY(angle.y); + if (angle.x != 0.0f) matrix.rotateX(angle.x); + if (angle.z != 0.0f) matrix.rotateZ(angle.z); + + TR::Animation *anim = &level->anims[animIndex]; + TR::Model &model = getModel(); + + float t; + vec3 move(0.0f); + TR::AnimFrame *frameA, *frameB; + getFrames(&frameA, &frameB, t, animIndex, animTime, true, &move); + + TR::Node *node = (int)model.node < level->nodesDataSize ? (TR::Node*)&level->nodesData[model.node] : NULL; + + matrix.translate(((vec3)frameA->pos).lerp(move + frameB->pos, t)); + + int sIndex = 0; + mat4 stack[20]; + + for (int i = 0; i < model.mCount; i++) { + + if (i > 0 && node) { + TR::Node &t = node[i - 1]; + + if (t.flags & 0x01) matrix = stack[--sIndex]; + if (t.flags & 0x02) stack[sIndex++] = matrix; + + ASSERT(sIndex >= 0 && sIndex < 20); + + matrix.translate(vec3(t.x, t.y, t.z)); + } + + if (i == index && !postRot) + return matrix; + + quat q; + if (animOverrideMask & (1 << i)) + q = animOverrides[i]; + else + q = lerpAngle(frameA->getAngle(i), frameB->getAngle(i), t); + matrix = matrix * mat4(q, vec3(0.0f)); + + if (i == index && postRot) + return matrix; + } + return matrix; + } + void updateEntity() { TR::Entity &e = getEntity(); e.x = int(pos.x); @@ -265,20 +344,11 @@ struct Controller { } virtual Box getBoundingBox() { - TR::Animation *anim = &level->anims[animIndex]; - TR::Model &model = getModel(); + float t; + TR::AnimFrame *frameA, *frameB; + getFrames(&frameA, &frameB, t, animIndex, animTime, true); - 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 *fA = (TR::AnimFrame*)&level->frameData[(anim->frameOffset + fIndexA * fSize) >> 1]; - TR::AnimFrame *fB = (TR::AnimFrame*)&level->frameData[(anim->frameOffset + fIndexB * fSize) >> 1]; - Box box(fA->box.min().lerp(fB->box.min(), k), fA->box.max().lerp(fB->box.max(), k)); + Box box(frameA->box.min().lerp(frameB->box.min(), t), frameA->box.max().lerp(frameB->box.max(), t)); box.rotate90(getEntity().rotation.value / 0x4000); box.min += pos; box.max += pos; @@ -454,6 +524,7 @@ struct Controller { virtual int getStateDeath() { return state; } virtual int getStateDefault() { return state; } virtual int getInputMask() { return 0; } + virtual void hit(int damage) { }; virtual int getState(Stand stand) { TR::Animation *anim = &level->anims[animIndex]; @@ -599,12 +670,12 @@ struct Controller { updateEnd(); } - void renderMesh(MeshBuilder *mesh, uint32 offsetIndex) { - MeshBuilder::MeshInfo *m = mesh->meshMap[offsetIndex]; - if (!m) return; // invisible mesh (offsetIndex > 0 && level.meshOffsets[offsetIndex] == 0) camera target entity etc. - - Core::active.shader->setParam(uModel, Core::mModel); - mesh->renderMesh(m); + void renderMesh(const mat4 &matrix, MeshBuilder *mesh, uint32 offsetIndex) { + MeshBuilder::MeshInfo *mInfo = mesh->meshMap[offsetIndex]; + if (!mInfo) return; // invisible mesh (offsetIndex > 0 && level.meshOffsets[offsetIndex] == 0) camera target entity etc. + + Core::active.shader->setParam(uModel, matrix); + mesh->renderMesh(mInfo); } void renderShadow(MeshBuilder *mesh, const vec3 &pos, const vec3 &offset, const vec3 &size, float angle) { @@ -617,60 +688,36 @@ struct Controller { Core::active.shader->setParam(uModel, m); Core::active.shader->setParam(uColor, vec4(0.0f, 0.0f, 0.0f, 0.5f)); + Core::active.shader->setParam(uAmbient, vec3(0.0f)); mesh->renderShadowSpot(); } virtual void render(Frustum *frustum, MeshBuilder *mesh) { - PROFILE_MARKER("MDL"); TR::Entity &entity = getEntity(); TR::Model &model = getModel(); - TR::Animation *anim; - float fTime; - vec3 angle; + TR::Animation *anim = &level->anims[animIndex]; - Controller *controller = (Controller*)entity.controller; - - anim = &level->anims[controller->animIndex]; - angle = controller->angle; - fTime = controller->animTime; - - if (angle.y != 0.0f) Core::mModel.rotateY(angle.y); - if (angle.x != 0.0f) Core::mModel.rotateX(angle.x); - if (angle.z != 0.0f) Core::mModel.rotateZ(angle.z); - - float k = fTime * 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; + mat4 matrix(Core::mModel); + matrix.translate(pos); + if (angle.y != 0.0f) matrix.rotateY(angle.y); + if (angle.x != 0.0f) matrix.rotateX(angle.x); + if (angle.z != 0.0f) matrix.rotateZ(angle.z); + float t; 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]; + TR::AnimFrame *frameA, *frameB; + getFrames(&frameA, &frameB, t, animIndex, animTime, true, &move); - vec3 bmin = frameA->box.min().lerp(frameB->box.min(), k); - vec3 bmax = frameA->box.max().lerp(frameB->box.max(), k); - if (frustum && !frustum->isVisible(Core::mModel, bmin, bmax)) + vec3 bmin = frameA->box.min().lerp(frameB->box.min(), t); + vec3 bmax = frameA->box.max().lerp(frameB->box.max(), t); + if (frustum && !frustum->isVisible(matrix, bmin, bmax)) return; + entity.flags.rendered = true; TR::Node *node = (int)model.node < level->nodesDataSize ? (TR::Node*)&level->nodesData[model.node] : NULL; - mat4 m; - m.identity(); - m.translate(((vec3)frameA->pos).lerp(move + frameB->pos, k)); + matrix.translate(((vec3)frameA->pos).lerp(move + frameB->pos, t)); int sIndex = 0; mat4 stack[20]; @@ -680,32 +727,28 @@ struct Controller { if (i > 0 && node) { TR::Node &t = node[i - 1]; - if (t.flags & 0x01) m = stack[--sIndex]; - if (t.flags & 0x02) stack[sIndex++] = m; + if (t.flags & 0x01) matrix = stack[--sIndex]; + if (t.flags & 0x02) stack[sIndex++] = matrix; ASSERT(sIndex >= 0 && sIndex < 20); - m.translate(vec3(t.x, t.y, t.z)); + matrix.translate(vec3(t.x, t.y, t.z)); } quat q; if (animOverrideMask & (1 << i)) q = animOverrides[i]; else - q = lerpAngle(frameA->getAngle(i), frameB->getAngle(i), k); - m = m * mat4(q, vec3(0.0f)); - - mat4 tmp = Core::mModel; - Core::mModel = Core::mModel * m; + q = lerpAngle(frameA->getAngle(i), frameB->getAngle(i), t); + matrix = matrix * mat4(q, vec3(0.0f)); + if (meshes) - renderMesh(mesh, meshes[i]); + renderMesh(matrix, mesh, meshes[i]); else - renderMesh(mesh, model.mStart + i); + renderMesh(matrix, mesh, model.mStart + i); if (joints) - joints[i] = Core::mModel; - - Core::mModel = tmp; + joints[i] = matrix; } if (TR::castShadow(entity.type)) { @@ -765,8 +808,9 @@ struct SpriteController : Controller { } virtual void render(Frustum *frustum, MeshBuilder *mesh) { - PROFILE_MARKER("SPR"); - Core::active.shader->setParam(uModel, Core::mModel); + mat4 m(Core::mModel); + m.translate(pos); + Core::active.shader->setParam(uModel, m); mesh->renderSprite(-(getEntity().modelIndex + 1), frame); } }; diff --git a/src/core.h b/src/core.h index 260b53b..21c3592 100644 --- a/src/core.h +++ b/src/core.h @@ -83,6 +83,8 @@ #endif #endif +#define MAX_LIGHTS 3 + struct Shader; struct Texture; @@ -113,8 +115,8 @@ namespace Core { float deltaTime; mat4 mView, mProj, mViewProj, mViewInv, mModel; vec3 viewPos; - vec3 lightPos; - vec4 lightColor; + vec3 lightPos[MAX_LIGHTS]; + vec4 lightColor[MAX_LIGHTS]; vec3 ambient; vec4 color; @@ -185,6 +187,9 @@ namespace Core { support.VAO = (void*)glBindVertexArray != NULL; Sound::init(); + + for (int i = 0; i < MAX_LIGHTS; i++) + lightColor[i] = vec4(0, 0, 0, 1); } void free() { diff --git a/src/enemy.h b/src/enemy.h index 8dfb9a9..9968fa2 100644 --- a/src/enemy.h +++ b/src/enemy.h @@ -4,11 +4,18 @@ #include "controller.h" struct Enemy : Controller { - Enemy(TR::Level *level, int entity) : Controller(level, entity) {} + int health; + + Enemy(TR::Level *level, int entity) : Controller(level, entity), health(100) {} virtual Stand getStand() { return STAND_GROUND; } + + virtual void hit(int damage) { + health -= damage; + }; + }; diff --git a/src/format.h b/src/format.h index c4698c7..746e34b 100644 --- a/src/format.h +++ b/src/format.h @@ -415,6 +415,10 @@ namespace TR { uint16 align; int16 modelIndex; // index of representation in models (index + 1) or spriteSequences (-(index + 1)) arrays void *controller; // Controller implementation or NULL + + bool isEnemy() { + return type >= ENEMY_TWIN && type <= ENEMY_LARSON; + } }; struct Animation { diff --git a/src/lara.h b/src/lara.h index 69676e9..e080642 100644 --- a/src/lara.h +++ b/src/lara.h @@ -25,6 +25,9 @@ #define MAX_TRIGGER_ACTIONS 64 #define DESCENT_SPEED 2048.0f +#define MUZZLE_FLASH_TIME 0.1f +#define FLASH_LIGHT_COLOR vec4(0.8f, 0.7f, 0.3f, 2048 * 2048) +#define TARGET_MAX_DIST (8.0f * 1024.0f) struct Lara : Controller { @@ -141,19 +144,19 @@ struct Lara : Controller { enum : int { BODY_HIP = 0x0001, - BODY_LEG_R1 = 0x0002, - BODY_LEG_R2 = 0x0004, - BODY_LEG_R3 = 0x0008, - BODY_LEG_L1 = 0x0010, - BODY_LEG_L2 = 0x0020, - BODY_LEG_L3 = 0x0040, + BODY_LEG_L1 = 0x0002, + BODY_LEG_L2 = 0x0004, + BODY_LEG_L3 = 0x0008, + BODY_LEG_R1 = 0x0010, + BODY_LEG_R2 = 0x0020, + BODY_LEG_R3 = 0x0040, BODY_CHEST = 0x0080, - BODY_ARM_L1 = 0x0100, - BODY_ARM_L2 = 0x0200, - BODY_ARM_L3 = 0x0400, - BODY_ARM_R1 = 0x0800, - BODY_ARM_R2 = 0x1000, - BODY_ARM_R3 = 0x2000, + BODY_ARM_R1 = 0x0100, + BODY_ARM_R2 = 0x0200, + BODY_ARM_R3 = 0x0400, + BODY_ARM_L1 = 0x0800, + BODY_ARM_L2 = 0x1000, + BODY_ARM_L3 = 0x2000, BODY_HEAD = 0x4000, BODY_ARM_L = BODY_ARM_L1 | BODY_ARM_L2 | BODY_ARM_L3, BODY_ARM_R = BODY_ARM_R1 | BODY_ARM_R2 | BODY_ARM_R3, @@ -172,24 +175,46 @@ struct Lara : Controller { } weapons[Weapon::MAX]; Weapon::Type wpnCurrent; - Weapon::State wpnState; - Weapon::Anim wpnAnim; - float wpnAnimTime; - float wpnAnimDir; - int wpnLastFrame; Weapon::Type wpnNext; - float wpnShotTime[2]; + Weapon::State wpnState; + vec3 chestOffset; - Lara(TR::Level *level, int entity) : Controller(level, entity), wpnCurrent(Weapon::EMPTY), wpnNext(Weapon::EMPTY) { + struct Arm { + int target; + int lastFrame; + float shotTimer; + float weight; + float animDir; + float animTime; + float animMaxTime; + int animIndex; + quat rot, rotAbs; + Weapon::Anim anim; + } arms[2]; + + int target; + quat rotHead, rotChest; + + int health; + float turnTime; + + Lara(TR::Level *level, int entity) : Controller(level, entity), wpnCurrent(Weapon::EMPTY), wpnNext(Weapon::EMPTY), chestOffset(pos), target(-1), health(100), turnTime(0.0f) { initMeshOverrides(); - wpnShotTime[0] = wpnShotTime[1] = 0.0f; + for (int i = 0; i < 2; i++) { + arms[i].shotTimer = MUZZLE_FLASH_TIME + 1.0f; + arms[i].animTime = 0.0f; + arms[i].rot = quat(0, 0, 0, 1); + arms[i].rotAbs = quat(0, 0, 0, 1); + } + + rotHead = rotChest = quat(0, 0, 0, 1); memset(weapons, -1, sizeof(weapons)); weapons[Weapon::PISTOLS].ammo = 0; weapons[Weapon::SHOTGUN].ammo = 9000; weapons[Weapon::MAGNUMS].ammo = 9000; weapons[Weapon::UZIS ].ammo = 9000; - setWeapon(Weapon::PISTOLS, Weapon::IS_HIDDEN); + wpnSet(Weapon::PISTOLS); #ifdef _DEBUG /* // gym @@ -211,7 +236,7 @@ struct Lara : Controller { pos = vec3(75671, -1024, 22862); angle = vec3(0.0f, -PI * 0.25f, 0.0f); getEntity().room = 13; - + // level 2 (room 1) pos = vec3(31400, -2560, 25200); angle = vec3(0.0f, PI, 0.0f); @@ -240,22 +265,49 @@ struct Lara : Controller { updateEntity(); #endif } + + void wpnSet(Weapon::Type wType) { + wpnCurrent = wType; + wpnState = Weapon::IS_FIRING; + wpnSetAnim(arms[0], Weapon::IS_HIDDEN, Weapon::Anim::NONE, 0.0f, 0.0f); + wpnSetAnim(arms[1], Weapon::IS_HIDDEN, Weapon::Anim::NONE, 0.0f, 0.0f); + } - void setWeapon(Weapon::Type wType, Weapon::State wState, Weapon::Anim wAnim = Weapon::Anim::NONE, float wAnimDir = 0.0f) { - wpnAnimDir = wAnimDir; + void wpnSetAnim(Arm &arm, Weapon::State wState, Weapon::Anim wAnim, float wAnimTime, float wAnimDir, bool playing = true) { + if (arm.anim != wAnim) + arm.lastFrame = 0xFFFF; - if (wAnim != wpnAnim) { - wpnAnim = wAnim; - TR::Animation *anim = &level->anims[getWeaponAnimIndex(wpnAnim)]; - wpnAnimTime = wpnAnimDir >= 0.0f ? 0.0f : ((anim->frameEnd - anim->frameStart) / 30.0f); - wpnLastFrame = 0xFFFF; + arm.anim = wAnim; + arm.animIndex = wpnGetAnimIndex(wAnim); + TR::Animation &anim = level->anims[arm.animIndex]; + + arm.animDir = playing ? wAnimDir : 0.0f; + arm.animMaxTime = (anim.frameEnd - anim.frameStart) / 30.0f; + + if (wAnimDir > 0.0f) + arm.animTime = wAnimTime; + else + if (wAnimDir < 0.0f) + arm.animTime = arm.animMaxTime + wAnimTime; + + wpnSetState(wState); + } + + int wpnGetDamage() { + switch (wpnCurrent) { + case Weapon::PISTOLS : return 10; + case Weapon::SHOTGUN : return 5; + case Weapon::MAGNUMS : return 20; + case Weapon::UZIS : return 5; + default : return 0; } + } - if (wpnCurrent == wType && wpnState == wState) - return; + void wpnSetState(Weapon::State wState) { + if (wpnState == wState) return; int mask = 0; - switch (wType) { + switch (wpnCurrent) { case Weapon::EMPTY : break; case Weapon::PISTOLS : case Weapon::MAGNUMS : @@ -280,30 +332,30 @@ struct Lara : Controller { if (wpnState == Weapon::IS_ARMED && wState == Weapon::IS_HIDDEN) playSound(TR::SND_HOLSTER, pos, Sound::Flags::PAN); int resetMask = BODY_HEAD | BODY_UPPER | BODY_LOWER; - if (wType == Weapon::SHOTGUN) + if (wpnCurrent == Weapon::SHOTGUN) resetMask &= ~(BODY_LEG_L1 | BODY_LEG_R1); // restore original meshes first meshSwap(level->models[Weapon::EMPTY], resetMask); // replace some parts - meshSwap(level->models[wType], mask); + meshSwap(level->models[wpnCurrent], mask); // have a shotgun in inventory place it on the back if another weapon is in use - if (wType != Weapon::SHOTGUN && weapons[Weapon::SHOTGUN].ammo != -1) + if (wpnCurrent != Weapon::SHOTGUN && weapons[Weapon::SHOTGUN].ammo != -1) meshSwap(level->models[Weapon::SHOTGUN], BODY_CHEST); // mesh swap to angry Lara's head while firing (from uzis model) if (wState == Weapon::IS_FIRING) meshSwap(level->models[Weapon::UZIS], BODY_HEAD); - wpnCurrent = wType; wpnState = wState; } bool emptyHands() { - return wpnState == Weapon::IS_HIDDEN; + return arms[0].anim == Weapon::Anim::NONE; } bool canDrawWeapon() { return wpnCurrent != Weapon::EMPTY + && emptyHands() && state != STATE_DEATH && state != STATE_HANG && state != STATE_REACH @@ -336,33 +388,39 @@ struct Lara : Controller { && state != STATE_WATER_OUT; } - void drawWeapon() { + bool wpnReady() { + return arms[0].anim != Weapon::Anim::PREPARE && arms[0].anim != Weapon::Anim::UNHOLSTER && arms[0].anim != Weapon::Anim::HOLSTER; + } + + void wpnDraw() { if (!canDrawWeapon()) return; - if (wpnAnim != Weapon::Anim::PREPARE && wpnAnim != Weapon::Anim::UNHOLSTER && wpnAnim != Weapon::Anim::HOLSTER && emptyHands()) { - bool isRifle = wpnCurrent == Weapon::SHOTGUN; - setWeapon(wpnCurrent, wpnState, isRifle ? Weapon::Anim::UNHOLSTER : Weapon::Anim::PREPARE, 1.0f); + if (wpnReady() && emptyHands()) { + if (wpnCurrent != Weapon::SHOTGUN) { + wpnSetAnim(arms[0], wpnState, Weapon::Anim::PREPARE, 0.0f, 1.0f); + wpnSetAnim(arms[1], wpnState, Weapon::Anim::PREPARE, 0.0f, 1.0f); + } else + wpnSetAnim(arms[0], wpnState, Weapon::Anim::UNHOLSTER, 0.0f, 1.0f); } } - void hideWeapon() { - if (wpnAnim != Weapon::Anim::PREPARE && wpnAnim != Weapon::Anim::UNHOLSTER && wpnAnim != Weapon::Anim::HOLSTER && !emptyHands()) { - bool isRifle = wpnCurrent == Weapon::SHOTGUN; - - if (isRifle) - setWeapon(wpnCurrent, wpnState, Weapon::Anim::HOLSTER, 1.0f); - else - setWeapon(wpnCurrent, wpnState, Weapon::Anim::UNHOLSTER, -1.0f); + void wpnHide() { + if (wpnReady() && !emptyHands()) { + if (wpnCurrent != Weapon::SHOTGUN) { + wpnSetAnim(arms[0], wpnState, Weapon::Anim::UNHOLSTER, 0.0f, -1.0f); + wpnSetAnim(arms[1], wpnState, Weapon::Anim::UNHOLSTER, 0.0f, -1.0f); + } else + wpnSetAnim(arms[0], wpnState, Weapon::Anim::HOLSTER, 0.0f, 1.0f); } } - void changeWeapon(Weapon::Type wType) { + void wpnChange(Weapon::Type wType) { if (wpnCurrent == wType) return; wpnNext = wType; - hideWeapon(); + wpnHide(); } - int getWeaponAnimIndex(Weapon::Anim wAnim) { + int wpnGetAnimIndex(Weapon::Anim wAnim) { int baseAnim = level->models[wpnCurrent == Weapon::SHOTGUN ? Weapon::SHOTGUN : Weapon::PISTOLS].animation; if (wpnCurrent == Weapon::SHOTGUN) { @@ -388,7 +446,7 @@ struct Lara : Controller { return 0; } - int getWeaponSound() { + int wpnGetSound() { switch (wpnCurrent) { case Weapon::PISTOLS : return TR::SND_PISTOLS_SHOT; case Weapon::SHOTGUN : return TR::SND_SHOTGUN_SHOT; @@ -398,185 +456,379 @@ struct Lara : Controller { } } - void doShot() { - playSound(getWeaponSound(), pos, Sound::Flags::PAN); + void wpnFire() { + bool armShot[2] = { false, false }; + for (int i = 0; i < 2; i++) { + int frameIndex = getFrameIndex(arms[i].animIndex, arms[i].animTime); + if (arms[i].anim == Weapon::Anim::FIRE) { + if (frameIndex < arms[i].lastFrame) { + if ((mask & ACTION) && (target == -1 || (target > -1 && arms[i].target > -1))) { + armShot[i] = true; + } else + wpnSetAnim(arms[i], Weapon::IS_ARMED, Weapon::Anim::AIM, 0.0f, -1.0f, target == -1); + } + // shotgun reload sound + if (wpnCurrent == Weapon::SHOTGUN) { + if (frameIndex >= 10 && arms[i].lastFrame < 10) + playSound(TR::SND_SHOTGUN_RELOAD, pos, Sound::Flags::PAN); + } + } + arms[i].lastFrame = frameIndex; + if (wpnCurrent == Weapon::SHOTGUN) break; + } + + if (armShot[0] || armShot[1]) + doShot(armShot[0], armShot[1]); + } + + void doShot(bool rightHand, bool leftHand) { int count = wpnCurrent == Weapon::SHOTGUN ? 6 : 2; float nearDist = 32.0f * 1024.0f; vec3 nearPos; + bool hasShot = false; for (int i = 0; i < count; i++) { - vec3 p = pos - vec3(0.0f, LARA_HANG_OFFSET, 0.0f); - vec3 d = getDir(); - vec3 r = d.cross(vec3(0, -1, 0)); // right dir - - if (wpnCurrent != Weapon::SHOTGUN) - p += r.normal() * ((i * 2 - 1) * 48); + Arm *arm; + int armIndex; + if (wpnCurrent == Weapon::SHOTGUN) { + if (!rightHand) continue; + arm = &arms[0]; + armIndex = 0; + } else { + if (!(i ? leftHand : rightHand)) continue; + arm = &arms[i]; + armIndex = i; + } - vec3 t = p + d * (24.0f * 1024.0f) + ((vec3(randf(), randf(), randf()) * 2.0f) - 1.0f) * 1024.0f; + arm->shotTimer = 0.0f; + hasShot = true; + + int joint = wpnCurrent == Weapon::SHOTGUN ? 8 : (i ? 11 : 8); + + vec3 p = getJoint(joint, false).getPos(); + vec3 d = arm->rotAbs * vec3(0, 0, 1); + vec3 t = p + d * (24.0f * 1024.0f) + ((vec3(randf(), randf(), randf()) * 2.0f) - vec3(1.0f)) * 1024.0f; int room; vec3 hit = trace(getRoomIndex(), p, t, room, false); - hit -= d * 64.0f; - addSprite(level, TR::Entity::SPARK, room, (int)hit.x, (int)hit.y, (int)hit.z, SpriteController::FRAME_RANDOM); + if (target > -1 && checkHit(target, p, hit, hit)) { + ((Controller*)level->entities[target].controller)->hit(wpnGetDamage()); + hit -= d * 64.0f; + addSprite(level, TR::Entity::BLOOD, room, (int)hit.x, (int)hit.y, (int)hit.z, SpriteController::FRAME_ANIMATED); + } else { + hit -= d * 64.0f; + addSprite(level, TR::Entity::SPARK, room, (int)hit.x, (int)hit.y, (int)hit.z, SpriteController::FRAME_RANDOM); - float dist = (hit - p).length(); - if (dist < nearDist) { - nearPos = hit; - nearDist = dist; + float dist = (hit - p).length(); + if (dist < nearDist) { + nearPos = hit; + nearDist = dist; + } } + + Core::lightPos[1 + armIndex] = getJoint(armIndex == 0 ? 10 : 13, false).getPos(); + Core::lightColor[1 + armIndex] = FLASH_LIGHT_COLOR; } - playSound(TR::SND_RICOCHET, nearPos, Sound::Flags::PAN); - - wpnShotTime[0] = wpnShotTime[1] = 0.0f; + if (hasShot) { + playSound(wpnGetSound(), pos, Sound::Flags::PAN); + playSound(TR::SND_RICOCHET, nearPos, Sound::Flags::PAN); + } } void updateWeapon() { - wpnShotTime[0] += Core::deltaTime; - wpnShotTime[1] += Core::deltaTime; + updateTargets(); + updateOverrides(); - TR::Animation *anim = &level->anims[getWeaponAnimIndex(wpnAnim)]; + 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 (Input::down[ik1]) changeWeapon(Weapon::PISTOLS); - if (Input::down[ik2]) changeWeapon(Weapon::SHOTGUN); - if (Input::down[ik3]) changeWeapon(Weapon::MAGNUMS); - if (Input::down[ik4]) changeWeapon(Weapon::UZIS); - - if (wpnNext != Weapon::EMPTY && wpnState == Weapon::IS_HIDDEN) { - setWeapon(wpnNext, Weapon::IS_HIDDEN); - drawWeapon(); + if (wpnNext != Weapon::EMPTY && emptyHands()) { + wpnSet(wpnNext); + wpnDraw(); wpnNext = Weapon::EMPTY; } // apply weapon state changes - if (wpnCurrent == Weapon::EMPTY) { - animOverrideMask &= ~(BODY_ARM_L | BODY_ARM_R); - return; - } - - Weapon::Anim nextAnim = wpnAnim; - - bool isRifle = wpnCurrent == Weapon::SHOTGUN; - if (mask & WEAPON) { if (emptyHands()) - drawWeapon(); + wpnDraw(); else - hideWeapon(); + wpnHide(); } if (!emptyHands()) { - if (mask & ACTION) { - if (wpnAnim == Weapon::Anim::HOLD) - setWeapon(wpnCurrent, wpnState, Weapon::Anim::AIM, 1.0f); - } else - if (wpnAnim == Weapon::Anim::AIM) - wpnAnimDir = -1.0f; + bool isRifle = wpnCurrent == Weapon::SHOTGUN; + + for (int i = 0; i < 2; i++) { + if (arms[i].target > -1 || ((mask & ACTION) && target == -1)) { + if (arms[i].anim == Weapon::Anim::HOLD) + wpnSetAnim(arms[i], wpnState, Weapon::Anim::AIM, 0.0f, 1.0f); + } else + if (arms[i].anim == Weapon::Anim::AIM) + arms[i].animDir = -1.0f; + + if (isRifle) break; + } + + for (int i = 0; i < 2; i++){ + arms[i].animTime += Core::deltaTime * arms[i].animDir; + arms[i].shotTimer += Core::deltaTime; + + float intensity = clamp((0.1f - arms[i].shotTimer) * 20.0f, 0.0f, 1.0f); + Core::lightColor[1 + i] = FLASH_LIGHT_COLOR * vec4(intensity, intensity, intensity, sqrtf(intensity)); + } + + if (isRifle) + animateShotgun(); + else + animatePistols(); + + wpnFire(); // make a shot } + } - anim = &level->anims[getWeaponAnimIndex(wpnAnim)]; - float maxTime = (anim->frameEnd - anim->frameStart) / 30.0f; + void animatePistols() { + for (int i = 0; i < 2; i++) { + Arm &arm = arms[i]; - if (wpnAnim == Weapon::Anim::NONE) { - animOverrideMask &= ~(BODY_ARM_L | BODY_ARM_R); - return; - } - animOverrideMask |= BODY_ARM_L | BODY_ARM_R; - - Weapon::Anim prevAnim = wpnAnim; // cache before changes - - wpnAnimTime += Core::deltaTime * wpnAnimDir; - - if (isRifle) { - if (wpnAnimDir > 0.0f) - switch (wpnAnim) { - case Weapon::Anim::UNHOLSTER : - if (wpnAnimTime >= maxTime) - setWeapon(wpnCurrent, Weapon::IS_ARMED, Weapon::Anim::HOLD, 0.0f); - else if (wpnAnimTime >= maxTime * 0.3f) - setWeapon(wpnCurrent, Weapon::IS_ARMED, wpnAnim, 1.0f); + if (arm.animDir >= 0.0f && arm.animTime >= arm.animMaxTime) + switch (arm.anim) { + case Weapon::Anim::PREPARE : wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::UNHOLSTER, arm.animTime - arm.animMaxTime, 1.0f); break; + case Weapon::Anim::UNHOLSTER : wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::HOLD, 0.0f, 1.0f, false); break; + case Weapon::Anim::AIM : + if (mask & ACTION) + wpnSetAnim(arm, Weapon::IS_FIRING, Weapon::Anim::FIRE, arm.animTime - arm.animMaxTime, wpnCurrent == Weapon::UZIS ? 2.0f : 1.0f); + else + wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::AIM, 0.0f, -1.0f, false); break; - case Weapon::Anim::HOLSTER : - if (wpnAnimTime >= maxTime) - setWeapon(wpnCurrent, Weapon::IS_HIDDEN, Weapon::Anim::NONE, wpnAnimDir); - else if (wpnAnimTime >= maxTime * 0.7f) - setWeapon(wpnCurrent, Weapon::IS_HIDDEN, wpnAnim, 1.0f); - break; - case Weapon::Anim::AIM : - if (wpnAnimTime >= maxTime) - setWeapon(wpnCurrent, Weapon::IS_FIRING, Weapon::Anim::FIRE, wpnAnimDir); - break; - default : ; - }; - - if (wpnAnimDir < 0.0f && wpnAnimTime <= 0.0f) - if (wpnAnim == Weapon::Anim::AIM) { - setWeapon(wpnCurrent, wpnState, Weapon::Anim::HOLD, 0.0f); - }; - } else { - if (wpnAnimDir > 0.0f && wpnAnimTime >= maxTime) - switch (wpnAnim) { - case Weapon::Anim::PREPARE : setWeapon(wpnCurrent, Weapon::IS_ARMED, Weapon::Anim::UNHOLSTER, wpnAnimDir); break; - case Weapon::Anim::UNHOLSTER : setWeapon(wpnCurrent, wpnState, Weapon::Anim::HOLD, 0.0f); break; - case Weapon::Anim::AIM : setWeapon(wpnCurrent, Weapon::IS_FIRING, Weapon::Anim::FIRE, wpnCurrent == Weapon::UZIS ? 2.0f : 1.0f); break; default : ; }; - if (wpnAnimDir < 0.0f && wpnAnimTime <= 0.0f) - switch (wpnAnim) { - case Weapon::Anim::PREPARE : setWeapon(wpnCurrent, wpnState, Weapon::Anim::NONE, wpnAnimDir); break; - case Weapon::Anim::UNHOLSTER : setWeapon(wpnCurrent, Weapon::IS_HIDDEN, Weapon::Anim::PREPARE, wpnAnimDir); break; - case Weapon::Anim::AIM : setWeapon(wpnCurrent, wpnState, Weapon::Anim::HOLD, 0.0f); break; + if (arm.animDir < 0.0f && arm.animTime <= 0.0f) + switch (arm.anim) { + case Weapon::Anim::PREPARE : wpnSetAnim(arm, Weapon::IS_HIDDEN, Weapon::Anim::NONE, 0.0f, 1.0f, false); break; + case Weapon::Anim::UNHOLSTER : wpnSetAnim(arm, Weapon::IS_HIDDEN, Weapon::Anim::PREPARE, arm.animTime, -1.0f); break; + case Weapon::Anim::AIM : wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::HOLD, 0.0f, 1.0f, false); break; default : ; }; } + } - if (prevAnim != wpnAnim) // check by cache - anim = &level->anims[getWeaponAnimIndex(wpnAnim)]; + void animateShotgun() { + Arm &arm = arms[0]; + if (arm.animDir >= 0.0f) + switch (arm.anim) { + case Weapon::Anim::UNHOLSTER : + if (arm.animTime >= arm.animMaxTime) + wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::HOLD, 0.0f, 1.0f, false); + else if (arm.animTime >= arm.animMaxTime * 0.3f) + wpnSetAnim(arm, Weapon::IS_ARMED, arm.anim, arm.animTime, 1.0f); + break; + case Weapon::Anim::HOLSTER : + if (arm.animTime >= arm.animMaxTime) + wpnSetAnim(arm, Weapon::IS_HIDDEN, Weapon::Anim::NONE, 0.0f, 1.0f, false); + else if (arm.animTime >= arm.animMaxTime * 0.7f) + wpnSetAnim(arm, Weapon::IS_HIDDEN, arm.anim, arm.animTime, 1.0f); + break; + case Weapon::Anim::AIM : + if (arm.animTime >= arm.animMaxTime) { + if (mask & ACTION) + wpnSetAnim(arm, Weapon::IS_FIRING, Weapon::Anim::FIRE, arm.animTime - arm.animMaxTime, 1.0f); + else + wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::AIM, 0.0f, -1.0f, false); + } + break; + default : ; + }; - // make a shot - int frameIndex = int(wpnAnimTime * 30.0f / anim->frameRate) % ((anim->frameEnd - anim->frameStart) / anim->frameRate + 1); - if (wpnAnim == Weapon::Anim::FIRE) { - if (frameIndex < wpnLastFrame) { - if (mask & ACTION) { - doShot(); - } else - setWeapon(wpnCurrent, Weapon::IS_ARMED, Weapon::Anim::AIM, -1.0f); + if (arm.animDir < 0.0f && arm.animTime <= 0.0f) + if (arm.anim == Weapon::Anim::AIM) + wpnSetAnim(arm, Weapon::IS_ARMED, Weapon::Anim::HOLD, 0.0f, 1.0f, false); + } + + void updateOverrides() { + // update animation overrides + TR::AnimFrame *frameA, *frameB; + float t; + + // head & chest + animOverrideMask |= BODY_CHEST | BODY_HEAD; + + getFrames(&frameA, &frameB, t, animIndex, animTime, true); + animOverrides[ 7] = lerpFrames(frameA, frameB, t, 7); + animOverrides[14] = lerpFrames(frameA, frameB, t, 14); + + + if (!emptyHands()) { + // right arm + Arm *arm = &arms[0]; + getFrames(&frameA, &frameB, t, arm->animIndex, arm->animTime); + animOverrides[ 8] = lerpFrames(frameA, frameB, t, 8); + animOverrides[ 9] = lerpFrames(frameA, frameB, t, 9); + animOverrides[10] = lerpFrames(frameA, frameB, t, 10); + // left arm + if (wpnCurrent != Weapon::SHOTGUN) arm = &arms[1]; + getFrames(&frameA, &frameB, t, arm->animIndex, arm->animTime); + animOverrides[11] = lerpFrames(frameA, frameB, t, 11); + animOverrides[12] = lerpFrames(frameA, frameB, t, 12); + animOverrides[13] = lerpFrames(frameA, frameB, t, 13); + + animOverrideMask |= BODY_ARM_R | BODY_ARM_L; + } else + animOverrideMask &= ~(BODY_ARM_R | BODY_ARM_L); + + lookAt(target); + + if (wpnCurrent == Weapon::SHOTGUN) + aimShotgun(); + else + aimPistols(); + } + + void lookAt(int target) { + float speed = 8.0f * Core::deltaTime; + quat rot; + + // chest + if ((stand == STAND_GROUND || stand == STAND_SLIDE) && aim(target, 7, vec4(-PI * 0.4f, PI * 0.4f, -PI * 0.9f, PI * 0.9f), rot)) + rotChest = rotChest.slerp(quat(0, 0, 0, 1).slerp(rot, 0.5f), speed); + else + rotChest = rotChest.slerp(quat(0, 0, 0, 1), speed); + animOverrides[7] = rotChest * animOverrides[7]; + + // head + if (aim(target, 14, vec4(-PI * 0.25f, PI * 0.25f, -PI * 0.5f, PI * 0.5f), rot)) + rotHead = rotHead.slerp(rot, speed); + else + rotHead = rotHead.slerp(quat(0, 0, 0, 1), speed); + animOverrides[14] = rotHead * animOverrides[14]; + } + + void aimShotgun() { + quat rot; + if (!aim(target, 14, vec4(-PI * 0.4f, PI * 0.4f, -PI * 0.25f, PI * 0.25f), rot, &arms[0].rotAbs)) + arms[0].target = -1; + } + + void aimPistols() { + float speed = 8.0f * Core::deltaTime; + + for (int i = 0; i < 2; i++) { + int joint = i ? 11 : 8; + vec4 range = i ? vec4(-PI * 0.4f, PI * 0.4f, -PI * 0.5f, PI * 0.2f) : vec4(-PI * 0.4f, PI * 0.4f, -PI * 0.2f, PI * 0.5f); + + Arm &arm = arms[i]; + quat rot; + if (!aim(target, joint, range, rot, &arm.rotAbs)) { + arm.target = -1; + rot = quat(0, 0, 0, 1); } - // shotgun reload sound - if (isRifle && frameIndex >= 10 && wpnLastFrame < 10) - playSound(TR::SND_SHOTGUN_RELOAD, pos, Sound::Flags::PAN); + + float t; + if (arm.anim == Weapon::Anim::FIRE) + t = 1.0f; + else if (arm.anim == Weapon::Anim::AIM) + t = arm.animTime / arm.animMaxTime; + else + t = 0.0f; + + arm.rot = arm.rot.slerp(rot, speed); + animOverrides[joint] = animOverrides[joint].slerp(arm.rot * animOverrides[joint], t); } - wpnLastFrame = frameIndex; + } + bool aim(int target, int joint, const vec4 &angleRange, quat &rot, quat *rotAbs = NULL) { + if (target > -1) { + TR::Entity &e = level->entities[target]; + vec3 t(e.x, e.y, e.z); - if (wpnAnim == Weapon::Anim::NONE) { - animOverrideMask &= ~(BODY_ARM_L | BODY_ARM_R); + mat4 m = getJoint(joint); + vec3 delta = (m.inverse() * t).normal(); + + float angleY = clampAngle(atan2(delta.x, delta.z)); + float angleX = clampAngle(asinf(delta.y)); + + if (angleX > angleRange.x && angleX <= angleRange.y && + angleY > angleRange.z && angleY <= angleRange.w) { + + quat ax(vec3(1, 0, 0), -angleX); + quat ay(vec3(0, 1, 0), angleY); + + rot = ay * ax; + if (rotAbs) + *rotAbs = m.getRot() * rot; + return true; + } + } + + if (rotAbs) + *rotAbs = rotYXZ(angle); + return false; + } + + void updateTargets() { + if (emptyHands() || !wpnReady()) { + target = arms[0].target = arms[1].target = -1; return; } - // update animation overrides - float k = wpnAnimTime * 30.0f / anim->frameRate; - int fIndex = (int)k; - int fCount = (anim->frameEnd - anim->frameStart) / anim->frameRate + 1; + if (!(mask & ACTION)) { + target = getTarget(); + arms[0].target = arms[1].target = target; + } else + if (target > -1) { + TR::Entity &e = level->entities[target]; + vec3 to = vec3(e.x, e.y, e.z); + vec3 from = pos - vec3(0, 512, 0); + arms[0].target = arms[1].target = checkOcclusion(from, to, (to - from).length()) ? target : -1; + } + } - int fSize = sizeof(TR::AnimFrame) + getModel().mCount * sizeof(uint16) * 2; - k = k - fIndex; + int getTarget() { + vec3 dir = getDir().normal(); + int dist = TARGET_MAX_DIST;// * TARGET_MAX_DIST; - int fIndexA = fIndex % fCount, fIndexB = (fIndex + 1) % fCount; - TR::AnimFrame *frameA = (TR::AnimFrame*)&level->frameData[(anim->frameOffset + fIndexA * fSize) >> 1]; - TR::AnimFrame *frameB = (TR::AnimFrame*)&level->frameData[(anim->frameOffset + fIndexB * fSize) >> 1]; + int index = -1; + for (int i = 0; i < level->entitiesCount; i++) { + TR::Entity &e = level->entities[i]; + if (!e.flags.rendered || !e.isEnemy()) continue; - // left arm - animOverrides[ 8] = lerpFrames(frameA, frameB, k, 8); - animOverrides[ 9] = lerpFrames(frameA, frameB, k, 9); - animOverrides[10] = lerpFrames(frameA, frameB, k, 10); - // right arm - animOverrides[11] = lerpFrames(frameA, frameB, k, 11); - animOverrides[12] = lerpFrames(frameA, frameB, k, 12); - animOverrides[13] = lerpFrames(frameA, frameB, k, 13); + vec3 p = vec3(e.x, e.y, e.z); + vec3 v = p - pos; + if (dir.dot(v.normal()) <= 0.5f) continue; // target is out of sigth -60..+60 degrees + + int d = v.length(); + if (d < dist && checkOcclusion(pos - vec3(0, 512, 0), p, d) ) { + index = i; + dist = d; + } + } + + return index; + } + + bool checkOcclusion(const vec3 &from, const vec3 &to, float dist) { + int room; + vec3 d = trace(getRoomIndex(), from, to, room, false); // check occlusion + return ((d - from).length() > (dist - 512.0f)); + } + + bool checkHit(int target, const vec3 &from, const vec3 &to, vec3 &point) { + TR::Entity &e = level->entities[target]; + Box box = ((Controller*)e.controller)->getBoundingBox(); + float t; + vec3 v = (to - from); + vec3 dir = v.normal(); + if (box.intersect(from, dir, t) && v.length() > t) { + point = from + dir * t; + return true; + } else + return false; } bool waterOut(int &outState) { @@ -747,50 +999,14 @@ struct Lara : Controller { activateNext(); } - vec3 getViewOffset() { - TR::Animation *anim = &level->anims[animIndex]; - TR::Model &model = getModel(); + vec3 getViewPoint() { + vec3 offset = chestOffset; + if (stand != STAND_UNDERWATER) + offset.y -= 256.0f; + if (wpnState != Weapon::IS_HIDDEN) + offset.y -= 256.0f; - 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; - if (wpnState != Weapon::IS_HIDDEN) - 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; } virtual Stand getStand() { @@ -811,7 +1027,7 @@ struct Lara : Controller { return stand; if (getRoom().flags.water) { - hideWeapon(); + wpnHide(); return STAND_UNDERWATER; } @@ -1173,8 +1389,8 @@ struct Lara : Controller { if (!lState) { lState = true; - static int snd_id = 0; - //playSound(snd_id, pos, 0); + static int snd_id = 81; + playSound(snd_id, pos, 0); LOG("sound: %d\n", snd_id++); /* @@ -1536,25 +1752,24 @@ struct Lara : Controller { } void renderMuzzleFlash(MeshBuilder *mesh, const mat4 &matrix, const vec3 &offset, float time) { - if (time > 0.1f) return; + if (time > MUZZLE_FLASH_TIME) return; float alpha = min(1.0f, (0.1f - time) * 20.0f); float lum = 3.0f; - mat4 tmp = Core::mModel; - Core::mModel = matrix; - Core::mModel.rotateX(-PI * 0.5f); - Core::mModel.translate(offset); + mat4 m(matrix); + m.rotateX(-PI * 0.5f); + m.translate(offset); Core::active.shader->setParam(uColor, vec4(lum, lum, lum, alpha)); - renderMesh(mesh, level->models[47].mStart); - Core::active.shader->setParam(uColor, Core::color); - Core::mModel = tmp; + renderMesh(m, mesh, level->models[47].mStart); } virtual void render(Frustum *frustum, MeshBuilder *mesh) { Controller::render(frustum, mesh); + chestOffset = joints[7].getPos(); + if (wpnCurrent != Weapon::SHOTGUN) { - renderMuzzleFlash(mesh, joints[10], vec3(-10, -50, 150), wpnShotTime[0]); - renderMuzzleFlash(mesh, joints[13], vec3( 10, -50, 150), wpnShotTime[1]); + renderMuzzleFlash(mesh, joints[10], vec3(-10, -50, 150), arms[0].shotTimer); + renderMuzzleFlash(mesh, joints[13], vec3( 10, -50, 150), arms[1].shotTimer); } } }; diff --git a/src/level.h b/src/level.h index d10a685..bebcd31 100644 --- a/src/level.h +++ b/src/level.h @@ -172,11 +172,11 @@ struct Level { void initShaders() { char def[255], ext[255]; - sprintf(def, "#define MAX_RANGES %d\n#define MAX_OFFSETS %d\n", mesh->animTexRangesCount, mesh->animTexOffsetsCount); + sprintf(def, "#define MAX_LIGHTS %d\n#define MAX_RANGES %d\n#define MAX_OFFSETS %d\n", MAX_LIGHTS, mesh->animTexRangesCount, mesh->animTexOffsetsCount); shaders[shStatic] = new Shader(SHADER, def); - sprintf(ext, "%s#define CAUSTICS\n", def); + sprintf(ext, "#define MAX_LIGHTS %d\n%s#define CAUSTICS\n", MAX_LIGHTS, def); shaders[shCaustics] = new Shader(SHADER, ext); - sprintf(ext, "%s#define SPRITE\n", def); + sprintf(ext, "#define MAX_LIGHTS %d\n%s#define SPRITE\n", MAX_LIGHTS, def); shaders[shSprite] = new Shader(SHADER, ext); } @@ -238,11 +238,12 @@ struct Level { sh->bind(); sh->setParam(uColor, Core::color); + sh->setParam(uLightColor, Core::lightColor[0], MAX_LIGHTS); + sh->setParam(uLightPos, Core::lightPos[0], MAX_LIGHTS); + sh->setParam(uAmbient, vec3(0.0f));//Core::ambient); // room static meshes { - PROFILE_MARKER("R_MESH"); - for (int i = 0; i < room.meshesCount; i++) { TR::Room::Mesh &rMesh = room.meshes[i]; if (rMesh.flags.rendered) continue; // skip if already rendered @@ -261,11 +262,17 @@ struct Level { // set light parameters getLight(offset, roomIndex); + if (rMesh.intensity >= 0) { + Core::ambient = vec3(intensity(rMesh.intensity) / 255.0f); + Core::ambient = vec3(0.0); + sh->setParam(uAmbient, Core::ambient); + } + // render static mesh mat4 mTemp = Core::mModel; Core::mModel.translate(offset); Core::mModel.rotateY(rMesh.rotation); - Core::active.shader->setParam(uModel, Core::mModel); + sh->setParam(uModel, Core::mModel); mesh->renderMesh(mesh->meshMap[sMesh->mesh]); Core::mModel = mTemp; } @@ -275,30 +282,30 @@ struct Level { if (!room.flags.rendered) { // skip if already rendered mat4 mTemp = Core::mModel; - { - PROFILE_MARKER("R_GEOM"); + room.flags.rendered = true; - room.flags.rendered = true; + Core::lightColor[0] = vec4(0, 0, 0, 1); + Core::ambient = vec3(0.0); - Core::lightColor = vec4(0.0f, 0.0f, 0.0f, 1.0f); - Core::ambient = vec3(1.0f); - sh->setParam(uLightColor, Core::lightColor); - sh->setParam(uAmbient, Core::ambient); + sh->setParam(uLightColor, Core::lightColor[0], MAX_LIGHTS); + sh->setParam(uLightPos, Core::lightPos[0], MAX_LIGHTS); + sh->setParam(uAmbient, Core::ambient); - Core::mModel.translate(offset); + Core::mModel.translate(offset); - // render room geometry - sh->setParam(uModel, Core::mModel); - mesh->renderRoomGeometry(roomIndex); - } + // render room geometry + sh->setParam(uModel, Core::mModel); + mesh->renderRoomGeometry(roomIndex); // render room sprites if (mesh->hasRoomSprites(roomIndex)) { - PROFILE_MARKER("R_SPR"); sh = shaders[shSprite]; sh->bind(); sh->setParam(uModel, Core::mModel); sh->setParam(uColor, Core::color); + sh->setParam(uLightColor, Core::lightColor[0], MAX_LIGHTS); + sh->setParam(uLightPos, Core::lightPos[0], MAX_LIGHTS); + sh->setParam(uAmbient, vec3(0.0f));//Core::ambient); mesh->renderRoomSprites(roomIndex); } @@ -353,17 +360,17 @@ struct Level { if (idx > -1) { TR::Room::Light &light = level.rooms[room].lights[idx]; float c = level.rooms[room].lights[idx].intensity / 8191.0f; - Core::lightPos = vec3(light.x, light.y, light.z); - Core::lightColor = vec4(c, c, c, (float)light.attenuation * (float)light.attenuation); + Core::lightPos[0] = vec3(light.x, light.y, light.z); + Core::lightColor[0] = vec4(c, c, c, (float)light.attenuation * (float)light.attenuation); } else { - Core::lightPos = vec3(0); - Core::lightColor = vec4(0, 0, 0, 1); + Core::lightPos[0] = vec3(0); + Core::lightColor[0] = vec4(0, 0, 0, 1); } Core::ambient = vec3(1.0f - level.rooms[roomIndex].ambient / 8191.0f); Core::active.shader->setParam(uAmbient, Core::ambient); - Core::active.shader->setParam(uLightPos, Core::lightPos); - Core::active.shader->setParam(uLightColor, Core::lightColor); + Core::active.shader->setParam(uLightPos, Core::lightPos[0], MAX_LIGHTS); + Core::active.shader->setParam(uLightColor, Core::lightColor[0], MAX_LIGHTS); } void renderEntity(const TR::Entity &entity) { @@ -371,12 +378,9 @@ struct Level { ASSERT(entity.controller); TR::Room &room = level.rooms[entity.room]; - if (!room.flags.rendered || entity.flags.invisible) // check for room visibility + if (!room.flags.rendered || entity.flags.invisible || entity.flags.rendered) return; - mat4 m = Core::mModel; - Core::mModel.translate(vec3(entity.x, entity.y, entity.z)); - float c = (entity.intensity > -1) ? (1.0f - entity.intensity / (float)0x1FFF) : 1.0f; float l = 1.0f; @@ -395,8 +399,6 @@ struct Level { Core::active.shader->setParam(uColor, Core::color); ((Controller*)entity.controller)->render(camera->frustum, mesh); - - Core::mModel = m; } void update() { @@ -446,6 +448,9 @@ struct Level { for (int j = 0; j < room.meshesCount; j++) room.meshes[j].flags.rendered = false; // clear visible flag for room static meshes } + + for (int i = 0; i < level.entitiesCount; i++) + level.entities[i].flags.rendered = false; } void renderRooms() { diff --git a/src/mesh.h b/src/mesh.h index e579e88..88335a9 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -106,6 +106,20 @@ struct Mesh { n.z = (int)o.z;\ }\ +#define CHECK_ROOM_NORMAL(n) \ + vec3 o(d.vertices[f.vertices[0]].vertex);\ + vec3 a = o - d.vertices[f.vertices[1]].vertex;\ + vec3 b = o - d.vertices[f.vertices[2]].vertex;\ + o = b.cross(a).normal() * 16300.0f;\ + n.x = (int)o.x;\ + n.y = (int)o.y;\ + n.z = (int)o.z; + + +uint8 intensity(int lighting) { + float a = 1.0f - (lighting >> 5) / 255.0f; + return int(255 * a * a); +} struct MeshBuilder { // rooms @@ -252,13 +266,14 @@ struct MeshBuilder { addQuad(indices, iCount, vCount, vStart, vertices, &t); + TR::Vertex n; + CHECK_ROOM_NORMAL(n); + for (int k = 0; k < 4; k++) { TR::Room::Data::Vertex &v = d.vertices[f.vertices[k]]; - uint8 a = 255 - (v.lighting >> 5); - - vertices[vCount].coord = { v.vertex.x, v.vertex.y, v.vertex.z, 0 }; - vertices[vCount].color = { a, a, a, 255 }; - vertices[vCount].normal = { 0, 0, 0, 1 }; + vertices[vCount].coord = { v.vertex.x, v.vertex.y, v.vertex.z, 0 }; + vertices[vCount].color = { 255, 255, 255, intensity(v.lighting) }; + vertices[vCount].normal = { n.x, n.y, n.z, 0 }; vCount++; } } @@ -269,13 +284,14 @@ struct MeshBuilder { addTriangle(indices, iCount, vCount, vStart, vertices, &t); + TR::Vertex n; + CHECK_ROOM_NORMAL(n); + for (int k = 0; k < 3; k++) { auto &v = d.vertices[f.vertices[k]]; - uint8 a = 255 - (v.lighting >> 5); - - vertices[vCount].coord = { v.vertex.x, v.vertex.y, v.vertex.z, 0 }; - vertices[vCount].color = { a, a, a, 255 }; - vertices[vCount].normal = { 0, 0, 0, 1 }; + vertices[vCount].coord = { v.vertex.x, v.vertex.y, v.vertex.z, 0 }; + vertices[vCount].color = { 255, 255, 255, intensity(v.lighting) }; + vertices[vCount].normal = { n.x, n.y, n.z, 0 }; vCount++; } } @@ -288,8 +304,7 @@ struct MeshBuilder { TR::Room::Data::Vertex &v = d.vertices[f.vertex]; TR::SpriteTexture &sprite = level.spriteTextures[f.texture]; - uint8 intensity = 255 - (v.lighting >> 5); - addSprite(indices, vertices, iCount, vCount, vStart, v.vertex.x, v.vertex.y, v.vertex.z, sprite, intensity); + addSprite(indices, vertices, iCount, vCount, vStart, v.vertex.x, v.vertex.y, v.vertex.z, sprite, intensity(v.lighting)); } } @@ -335,21 +350,26 @@ struct MeshBuilder { addQuad(indices, iCount, vCount, vStart, vertices, &t); + short4 normal; + if (!normals) { + TR::Vertex n = { 0, 0, 0 }; + CHECK_NORMAL(n); + normal = { n.x, n.y, n.z, 0 }; + } + for (int k = 0; k < 4; k++) { - uint16 idx = f.vertices[k]; - TR::Vertex &v = mVertices[idx]; + TR::Vertex &v = mVertices[f.vertices[k]]; vertices[vCount].coord = { v.x, v.y, v.z, 0 }; if (normals) { - TR::Vertex &n = normals[idx]; + TR::Vertex &n = normals[f.vertices[k]]; CHECK_NORMAL(n); vertices[vCount].normal = { n.x, n.y, n.z, 0 }; - vertices[vCount].color = { 255, 255, 255, 255 }; + vertices[vCount].color = { 255, 255, 255, 0 }; } else { - uint8 a = 255 - (lights[idx] >> 5); - vertices[vCount].normal = { 0, 0, 0, 1 }; - vertices[vCount].color = { a, a, a, 255 }; + vertices[vCount].normal = normal; + vertices[vCount].color = { 255, 255, 255, intensity(lights[f.vertices[k]]) }; } vCount++; } @@ -363,19 +383,25 @@ struct MeshBuilder { addTriangle(indices, iCount, vCount, vStart, vertices, &t); + short4 normal; + if (!normals) { + TR::Vertex n = { 0, 0, 0 }; + CHECK_NORMAL(n); + normal = { n.x, n.y, n.z, 0 }; + } + for (int k = 0; k < 3; k++) { auto &v = mVertices[f.vertices[k]]; - vertices[vCount].coord = { v.x, v.y, v.z, 0 }; + vertices[vCount].coord = { v.x, v.y, v.z, 0 }; - if (nCount > 0) { + if (normals) { TR::Vertex &n = normals[f.vertices[k]]; CHECK_NORMAL(n); vertices[vCount].normal = { n.x, n.y, n.z, 0 }; - vertices[vCount].color = { 255, 255, 255, 255 }; + vertices[vCount].color = { 255, 255, 255, 0 }; } else { - uint8 a = 255 - (lights[f.vertices[k]] >> 5); - vertices[vCount].normal = { 0, 0, 0, 1 }; - vertices[vCount].color = { a, a, a, 255 }; + vertices[vCount].normal = normal; + vertices[vCount].color = { 255, 255, 255, intensity(lights[f.vertices[k]]) }; } vCount++; } @@ -389,20 +415,26 @@ struct MeshBuilder { addQuad(indices, iCount, vCount, vStart, vertices, &whiteTileQuad); + short4 normal; + if (!normals) { + TR::Vertex n = { 0, 0, 0 }; + CHECK_NORMAL(n); + normal = { n.x, n.y, n.z, 0 }; + } + for (int k = 0; k < 4; k++) { auto &v = mVertices[f.vertices[k]]; - vertices[vCount].coord = { v.x, v.y, v.z, 0 }; + vertices[vCount].coord = { v.x, v.y, v.z, 0 }; - if (nCount > 0) { + if (normals) { TR::Vertex &n = normals[f.vertices[k]]; CHECK_NORMAL(n); vertices[vCount].normal = { n.x, n.y, n.z, 0 }; - vertices[vCount].color = { c.r, c.g, c.b, 255 }; + vertices[vCount].color = { c.r, c.g, c.b, 0 }; } else { - uint8 a = 255 - (lights[f.vertices[k]] >> 5); - vertices[vCount].normal = { 0, 0, 0, 1 }; - vertices[vCount].color = { a, a, a, 255 }; + vertices[vCount].normal = normal; + vertices[vCount].color = { c.r, c.g, c.b, intensity(lights[f.vertices[k]]) }; } vCount++; } @@ -419,17 +451,23 @@ struct MeshBuilder { for (int k = 0; k < 3; k++) { auto &v = mVertices[f.vertices[k]]; - vertices[vCount].coord = { v.x, v.y, v.z, 0 }; + vertices[vCount].coord = { v.x, v.y, v.z, 0 }; - if (nCount > 0) { + short4 normal; + if (!normals) { + TR::Vertex n = { 0, 0, 0 }; + CHECK_NORMAL(n); + normal = { n.x, n.y, n.z, 0 }; + } + + if (normals) { TR::Vertex &n = normals[f.vertices[k]]; CHECK_NORMAL(n); vertices[vCount].normal = { n.x, n.y, n.z, 0 }; - vertices[vCount].color = { c.r, c.g, c.b, 255 }; + vertices[vCount].color = { c.r, c.g, c.b, 0 }; } else { - uint8 a = 255 - (lights[f.vertices[k]] >> 5); - vertices[vCount].normal = { 0, 0, 0, 1 }; - vertices[vCount].color = { a, a, a, 255 }; + vertices[vCount].normal = normal; + vertices[vCount].color = { c.r, c.g, c.b, intensity(lights[f.vertices[k]]) }; } vCount++; } @@ -450,8 +488,8 @@ struct MeshBuilder { // build shadow spot for (int i = 0; i < 8; i++) { Vertex &v = vertices[vCount + i]; - v.normal = { 0, 0, 0, 1 }; - v.color = { 255, 255, 255, 255 }; + v.normal = { 0, -1, 0, 0 }; + v.color = { 255, 255, 255, 0 }; v.texCoord = { 32688, 32688, 0, 0 }; float a = i * (PI / 4.0f) + (PI / 8.0f); @@ -618,7 +656,7 @@ struct MeshBuilder { quad[0].coord = quad[1].coord = quad[2].coord = quad[3].coord = { x, y, 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 = { intensity, intensity, intensity, 255 }; + 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; diff --git a/src/platform/web/index.html b/src/platform/web/index.html index d9f5488..d8cee04 100644 --- a/src/platform/web/index.html +++ b/src/platform/web/index.html @@ -3,10 +3,9 @@