diff --git a/bin/OpenLara.exe b/bin/OpenLara.exe
index 49b3094..c350d1d 100644
Binary files a/bin/OpenLara.exe and b/bin/OpenLara.exe differ
diff --git a/src/camera.h b/src/camera.h
index 954f4b2..f66efd7 100644
--- a/src/camera.h
+++ b/src/camera.h
@@ -2,158 +2,12 @@
#define H_CAMERA
#include "core.h"
+#include "frustum.h"
#include "controller.h"
#include "lara.h"
-#define MAX_CLIP_PLANES 16
-
#define CAMERA_OFFSET (1024.0f + 256.0f)
-struct Frustum {
-
- struct Poly {
- vec3 vertices[MAX_CLIP_PLANES];
- int count;
- };
-
- vec3 pos;
- vec4 planes[MAX_CLIP_PLANES * 2]; // + buffer for OBB visibility test
- int start, count;
-#ifdef _DEBUG
- int dbg;
- Poly debugPoly;
-#endif
-
- void calcPlanes(const mat4 &m) {
- #ifdef _DEBUG
- dbg = 0;
- #endif
- start = 0;
- count = 5;
- planes[0] = vec4(m.e30 - m.e20, m.e31 - m.e21, m.e32 - m.e22, m.e33 - m.e23); // near
- planes[1] = vec4(m.e30 - m.e10, m.e31 - m.e11, m.e32 - m.e12, m.e33 - m.e13); // top
- planes[2] = vec4(m.e30 - m.e00, m.e31 - m.e01, m.e32 - m.e02, m.e33 - m.e03); // right
- planes[3] = vec4(m.e30 + m.e10, m.e31 + m.e11, m.e32 + m.e12, m.e33 + m.e13); // bottom
- planes[4] = vec4(m.e30 + m.e00, m.e31 + m.e01, m.e32 + m.e02, m.e33 + m.e03); // left
- for (int i = 0; i < count; i++)
- planes[i] *= 1.0f / planes[i].xyz.length();
- }
-
- void calcPlanes(const Poly &poly) {
- count = 1 + poly.count; // add one for near plane (not changing)
- ASSERT(count < MAX_CLIP_PLANES);
- if (count < 4) return;
-
- vec3 e1 = poly.vertices[0] - pos;
- for (int i = 1; i < count; i++) {
- vec3 e2 = poly.vertices[i % poly.count] - pos;
- planes[i].xyz = e1.cross(e2).normal();
- planes[i].w = -(pos.dot(planes[i].xyz));
- e1 = e2;
- }
- }
-
- void clipPlane(const Poly &src, Poly &dst, const vec4 &plane) {
- dst.count = 0;
- if (!src.count) return;
-
- float t1 = src.vertices[0].dot(plane.xyz) + plane.w;
-
- for (int i = 0; i < src.count; i++) {
- const vec3 &v1 = src.vertices[i];
- const vec3 &v2 = src.vertices[(i + 1) % src.count];
-
- float t2 = v2.dot(plane.xyz) + plane.w;
-
- // hack for big float numbers
- 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) { // check for opposite signs
- float k1 = t2 / (t2 - t1);
- float k2 = t1 / (t2 - t1);
- dst.vertices[dst.count++] = v1 * (float)k1 - v2 * (float)k2;
- ASSERT(dst.count < MAX_CLIP_PLANES);
- }
-
- t1 = t2;
- }
- }
-
- bool clipByPortal(const vec3 *vertices, int vCount, const vec3 &normal) {
- if (normal.dot(pos - vertices[0]) < 0.0f) // check portal winding order
- return false;
-
- Poly poly[2];
-
- poly[0].count = vCount;
- memmove(poly[0].vertices, vertices, sizeof(vec3) * poly[0].count);
-#ifdef _DEBUG
- debugPoly.count = 0;
-#endif
- int j = 0;
- for (int i = 1; i < count; i++, j ^= 1)
- clipPlane(poly[j], poly[j ^ 1], planes[i]);
-
- calcPlanes(poly[j]);
- return count >= 4;
- }
-
- // AABB visibility check
- bool isVisible(const vec3 &min, const vec3 &max) const {
- if (count < 4) return false;
-
- for (int i = start; i < start + count; i++) {
- const vec3 &n = planes[i].xyz;
- const float d = -planes[i].w;
-
- if (n.dot(max) < d &&
- n.dot(min) < d &&
- n.dot(vec3(min.x, max.y, max.z)) < d &&
- n.dot(vec3(max.x, min.y, max.z)) < d &&
- n.dot(vec3(min.x, min.y, max.z)) < d &&
- n.dot(vec3(max.x, max.y, min.z)) < d &&
- n.dot(vec3(min.x, max.y, min.z)) < d &&
- n.dot(vec3(max.x, min.y, min.z)) < d)
- return false;
- }
- return true;
- }
-
- // OBB visibility check
- bool isVisible(const mat4 &matrix, const vec3 &min, const vec3 &max) {
- start = count;
- // transform clip planes (relative)
- mat4 m = matrix.inverse();
- for (int i = 0; i < count; i++) {
- vec4 &p = planes[i];
- vec4 o = m * vec4(p.xyz * (-p.w), 1.0f);
- vec4 n = m * vec4(p.xyz, 0.0f);
- planes[start + i] = vec4(n.xyz, -n.xyz.dot(o.xyz));
- }
- bool visible = isVisible(min, max);
- start = 0;
- return visible;
- }
-
- // Sphere visibility check
- bool isVisible(const vec3 ¢er, float radius) {
- if (count < 4) return false;
-
- for (int i = 0; i < count; i++)
- if (planes[i].xyz.dot(center) + planes[i].w < -radius)
- return false;
- return true;
- }
-
-};
-
-
struct Camera : Controller {
Lara *owner;
Frustum *frustum;
@@ -165,6 +19,7 @@ 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;
@@ -176,6 +31,7 @@ struct Camera : Controller {
room = owner->getEntity().room;
pos = pos - owner->getDir() * 1024.0f;
}
+ viewOffset = owner->getViewOffset();
}
virtual ~Camera() {
@@ -234,8 +90,11 @@ 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);
+
vec3 dir;
- target = vec3(owner->pos.x, owner->pos.y, owner->pos.z) + owner->getViewOffset();
+ target = vec3(owner->pos.x, owner->pos.y, owner->pos.z) + viewOffset;
if (actCamera > -1) {
TR::Camera &c = level->cameras[actCamera];
@@ -253,24 +112,25 @@ struct Camera : Controller {
} else
dir = getDir();
- if (owner->state != Lara::STATE_BACK_JUMP || actTargetEntity > -1) {
+ int destRoom;
+ if ((owner->wpnState != Lara::Weapon::IS_HIDDEN || owner->state != Lara::STATE_BACK_JUMP) || actTargetEntity > -1) {
vec3 eye = target - dir * CAMERA_OFFSET;
- destPos = trace(owner->getRoomIndex(), target, eye);
+ destPos = trace(owner->getRoomIndex(), target, eye, destRoom, true);
lastDest = destPos;
} else {
vec3 eye = lastDest + dir.cross(vec3(0, 1, 0)).normal() * 2048.0f - vec3(0.0f, 512.0f, 0.0f);
- destPos = trace(owner->getRoomIndex(), target, eye);
+ destPos = trace(owner->getRoomIndex(), target, eye, destRoom, true);
}
}
- float lerpFactor = (actTargetEntity == -1) ? 2.0f : 10.0f;
-
pos = pos.lerp(destPos, Core::deltaTime * lerpFactor);
if (actCamera <= -1) {
TR::Level::FloorInfo info;
level->getFloorInfo(room, (int)pos.x, (int)pos.z, info);
+ int lastRoom = room;
+
if (info.roomNext != 255)
room = info.roomNext;
@@ -289,72 +149,16 @@ struct Camera : Controller {
if (info.floor != 0xffff8100)
pos.y = info.floor;
}
+
+ // play underwater sound when camera goes under water
+ if (lastRoom != room && !level->rooms[lastRoom].flags.water && level->rooms[room].flags.water)
+ playSound(TR::SND_UNDERWATER, vec3(0.0f), 0);
}
mViewInv = mat4(pos, target, vec3(0, -1, 0));
Sound::listener.matrix = mViewInv;
}
- vec3 trace(int fromRoom, const vec3 &from, const vec3 &to) { // TODO: use Bresenham
- int room = fromRoom;
-
- vec3 pos = from, dir = to - from;
- int px = (int)pos.x, py = (int)pos.y, pz = (int)pos.z;
-
- float dist = dir.length();
- dir = dir * (1.0f / dist);
-
- int lr = -1, lx = -1, lz = -1;
- TR::Level::FloorInfo info;
- while (dist > 1.0f) {
- int sx = px / 1024 * 1024 + 512,
- sz = pz / 1024 * 1024 + 512;
-
- if (lr != room || lx != sx || lz != sz) {
- level->getFloorInfo(room, sx, sz, info);
- if (info.roomNext != 0xFF) {
- room = info.roomNext;
- level->getFloorInfo(room, sx, sz, info);
- }
- lr = room;
- lx = sx;
- lz = sz;
- }
-
- if (py > info.floor && info.roomBelow != 0xFF)
- room = info.roomBelow;
- else if (py < info.ceiling && info.roomAbove != 0xFF)
- room = info.roomAbove;
- else if (py > info.floor || py < info.ceiling) {
- int minX = px / 1024 * 1024;
- int minZ = pz / 1024 * 1024;
- int maxX = minX + 1024;
- int maxZ = minZ + 1024;
-
- pos = vec3(clamp(px, minX, maxX), pos.y, clamp(pz, minZ, maxZ)) + boxNormal(px, pz) * 256.0f;
- dir = (pos - from).normal();
- }
-
- float d = min(dist, 128.0f); // STEP = 128
- dist -= d;
- pos = pos + dir * d;
-
- px = (int)pos.x, py = (int)pos.y, pz = (int)pos.z;
- }
-
- return pos;
- }
-
- vec3 boxNormal(int x, int z) {
- x %= 1024;
- z %= 1024;
-
- if (x > 1024 - z)
- return x < z ? vec3(0, 0, 1) : vec3(1, 0, 0);
- else
- return x < z ? vec3(-1, 0, 0) : vec3(0, 0, -1);
- }
-
virtual void setup() {
Core::mViewInv = mViewInv;
Core::mView = Core::mViewInv.inverse();
diff --git a/src/controller.h b/src/controller.h
index b286e72..02aac82 100644
--- a/src/controller.h
+++ b/src/controller.h
@@ -2,6 +2,8 @@
#define H_CONTROLLER
#include "format.h"
+#include "frustum.h"
+#include "mesh.h"
#define GRAVITY 6.0f
#define NO_OVERLAP 0x7FFFFFFF
@@ -43,6 +45,9 @@ struct Controller {
int *meshes;
int mCount;
+ quat *animOverrides; // left & right arms animation frames
+ int animOverrideMask;
+ mat4 *joints;
struct ActionCommand {
TR::Action action;
@@ -54,7 +59,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) {
+ 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) {
TR::Entity &e = getEntity();
pos = vec3((float)e.x, (float)e.y, (float)e.z);
angle = vec3(0.0f, e.rotation, 0.0f);
@@ -62,14 +67,25 @@ struct Controller {
animIndex = e.modelIndex > 0 ? getModel().animation : 0;
state = level->anims[animIndex].state;
TR::Model &model = getModel();
- mCount = model.mCount;
- meshes = mCount ? new int[mCount] : NULL;
- for (int i = 0; i < mCount; i++)
- meshes[i] = model.mStart + i;
}
virtual ~Controller() {
delete[] meshes;
+ delete[] animOverrides;
+ delete[] joints;
+ }
+
+ void initMeshOverrides() {
+ TR::Model &model = getModel();
+ mCount = model.mCount;
+ meshes = mCount ? new int[mCount] : NULL;
+ for (int i = 0; i < mCount; i++)
+ meshes[i] = model.mStart + i;
+
+ animOverrides = new quat[model.mCount];
+ animOverrideMask = 0;
+
+ joints = new mat4[model.mCount];
}
void meshSwap(TR::Model &model, int mask) {
@@ -269,6 +285,77 @@ struct Controller {
return box;
}
+ vec3 trace(int fromRoom, const vec3 &from, const vec3 &to, int &room, bool isCamera) { // TODO: use Bresenham
+ room = fromRoom;
+
+ vec3 pos = from, dir = to - from;
+ int px = (int)pos.x, py = (int)pos.y, pz = (int)pos.z;
+
+ float dist = dir.length();
+ dir = dir * (1.0f / dist);
+
+ int lr = -1, lx = -1, lz = -1;
+ TR::Level::FloorInfo info;
+ while (dist > 1.0f) {
+ int sx = px / 1024 * 1024 + 512,
+ sz = pz / 1024 * 1024 + 512;
+
+ if (lr != room || lx != sx || lz != sz) {
+ level->getFloorInfo(room, sx, sz, info);
+ if (info.roomNext != 0xFF) {
+ room = info.roomNext;
+ level->getFloorInfo(room, sx, sz, info);
+ }
+ lr = room;
+ lx = sx;
+ lz = sz;
+ }
+
+ if (isCamera) {
+ if (py > info.floor && info.roomBelow != 0xFF)
+ room = info.roomBelow;
+ else if (py < info.ceiling && info.roomAbove != 0xFF)
+ room = info.roomAbove;
+ else if (py > info.floor || py < info.ceiling) {
+ int minX = px / 1024 * 1024;
+ int minZ = pz / 1024 * 1024;
+ int maxX = minX + 1024;
+ int maxZ = minZ + 1024;
+
+ pos = vec3(clamp(px, minX, maxX), pos.y, clamp(pz, minZ, maxZ)) + boxNormal(px, pz) * 256.0f;
+ dir = (pos - from).normal();
+ }
+ } else {
+ if (py > info.floor) {
+ if (info.roomBelow != 0xFF)
+ room = info.roomBelow;
+ else
+ break;
+ }
+
+ if (py < info.ceiling) {
+ if (info.roomAbove != 0xFF)
+ room = info.roomAbove;
+ else
+ break;
+ }
+ }
+
+ float d = min(dist, 32.0f); // STEP = 32
+ dist -= d;
+ pos = pos + dir * d;
+
+ px = (int)pos.x, py = (int)pos.y, pz = (int)pos.z;
+ }
+
+ return pos;
+ }
+
+ void doBubbles() {
+ if (rand() % 10 <= 6) return;
+ playSound(TR::SND_BUBBLE, pos, Sound::Flags::PAN);
+ }
+
void collide() {
TR::Entity &entity = getEntity();
@@ -482,7 +569,7 @@ struct Controller {
if (cmd == TR::ANIM_CMD_EFFECT) {
switch (id) {
case TR::EFFECT_ROTATE_180 : angle.y = angle.y + PI; break;
- case TR::EFFECT_LARA_BUBBLES : if (rand() % 10 > 6) playSound(TR::SND_BUBBLE, pos, Sound::Flags::PAN); break;
+ case TR::EFFECT_LARA_BUBBLES : doBubbles(); break;
case TR::EFFECT_LARA_HANDSFREE : break;
default : LOG("unknown special cmd %d (anim %d)\n", id, animIndex);
}
@@ -511,6 +598,122 @@ struct Controller {
updateVelocity();
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 renderShadow(MeshBuilder *mesh, const vec3 &pos, const vec3 &offset, const vec3 &size, float angle) {
+ mat4 m;
+ m.identity();
+ m.translate(pos);
+ m.rotateY(angle);
+ m.translate(vec3(offset.x, 0.0f, offset.z));
+ m.scale(vec3(size.x, 0.0f, size.z) * (1.0f / 1024.0f));
+
+ Core::active.shader->setParam(uModel, m);
+ Core::active.shader->setParam(uColor, vec4(0.0f, 0.0f, 0.0f, 0.5f));
+ 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;
+
+ 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;
+
+ 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];
+
+ 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))
+ return;
+
+ 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));
+
+ 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) m = stack[--sIndex];
+ if (t.flags & 0x02) stack[sIndex++] = m;
+
+ ASSERT(sIndex >= 0 && sIndex < 20);
+
+ m.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;
+ if (meshes)
+ renderMesh(mesh, meshes[i]);
+ else
+ renderMesh(mesh, model.mStart + i);
+
+ if (joints)
+ joints[i] = Core::mModel;
+
+ Core::mModel = tmp;
+ }
+
+ if (TR::castShadow(entity.type)) {
+ TR::Level::FloorInfo info;
+ level->getFloorInfo(entity.room, entity.x, entity.z, info, true);
+ renderShadow(mesh, vec3(entity.x, info.floor - 16.0f, entity.z), (bmax + bmin) * 0.5f, (bmax - bmin) * 0.8f, entity.rotation);
+ }
+ }
};
@@ -521,10 +724,10 @@ struct SpriteController : Controller {
FRAME_RANDOM = -2,
};
- int frame;
- bool instant, animated;
+ int frame, flag;
+ bool instant;
- SpriteController(TR::Level *level, int entity, bool instant = true, int frame = FRAME_ANIMATED) : Controller(level, entity), instant(instant), animated(frame == FRAME_ANIMATED) {
+ SpriteController(TR::Level *level, int entity, bool instant = true, int frame = FRAME_ANIMATED) : Controller(level, entity), instant(instant), flag(frame) {
if (frame >= 0) { // specific frame
this->frame = frame;
} else if (frame == FRAME_RANDOM) { // random frame
@@ -539,10 +742,12 @@ struct SpriteController : Controller {
}
void update() {
+ if (flag >= 0) return;
+
bool remove = false;
animTime += Core::deltaTime;
- if (animated) {
+ if (flag == FRAME_ANIMATED) {
frame = int(animTime * SPRITE_FPS);
TR::SpriteSequence &seq = getSequence();
if (instant && frame >= seq.sCount)
@@ -558,6 +763,12 @@ struct SpriteController : Controller {
delete this;
}
}
+
+ virtual void render(Frustum *frustum, MeshBuilder *mesh) {
+ PROFILE_MARKER("SPR");
+ Core::active.shader->setParam(uModel, Core::mModel);
+ mesh->renderSprite(-(getEntity().modelIndex + 1), frame);
+ }
};
void addSprite(TR::Level *level, TR::Entity::Type type, int room, int x, int y, int z, int frame = -1) {
@@ -565,7 +776,7 @@ void addSprite(TR::Level *level, TR::Entity::Type type, int room, int x, int y,
if (index > -1) {
level->entities[index].intensity = 0x1FFF - level->rooms[room].ambient;
level->entities[index].controller = new SpriteController(level, index, true, frame);
- }
+ }
}
#endif
\ No newline at end of file
diff --git a/src/format.h b/src/format.h
index d52459c..c4698c7 100644
--- a/src/format.h
+++ b/src/format.h
@@ -70,11 +70,40 @@ namespace TR {
};
enum {
- SND_NO = 2,
- SND_LANDING = 4,
- SND_BUBBLE = 37,
- SND_DART = 151,
- SND_SECRET = 173,
+ SND_NO = 2,
+
+ SND_LANDING = 4,
+
+ SND_UNHOLSTER = 6,
+ SND_HOLSTER = 7,
+ SND_PISTOLS_SHOT = 8,
+ SND_SHOTGUN_RELOAD = 9,
+ SND_RICOCHET = 10,
+
+ SND_SCREAM = 30,
+ SND_HIT = 31,
+
+ SND_BUBBLE = 37,
+
+ SND_UZIS_SHOT = 43,
+ SND_MAGNUMS_SHOT = 44,
+ SND_SHOTGUN_SHOT = 45,
+
+ 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_HEALTH = 116,
+
+ SND_DART = 151,
+
+ SND_SECRET = 173,
};
enum Action : uint16 {
diff --git a/src/frustum.h b/src/frustum.h
new file mode 100644
index 0000000..2e5a523
--- /dev/null
+++ b/src/frustum.h
@@ -0,0 +1,152 @@
+#ifndef H_FRUSTUM
+#define H_FRUSTUM
+
+#include "utils.h"
+
+#define MAX_CLIP_PLANES 16
+
+struct Frustum {
+
+ struct Poly {
+ vec3 vertices[MAX_CLIP_PLANES];
+ int count;
+ };
+
+ vec3 pos;
+ vec4 planes[MAX_CLIP_PLANES * 2]; // + buffer for OBB visibility test
+ int start, count;
+#ifdef _DEBUG
+ int dbg;
+ Poly debugPoly;
+#endif
+
+ void calcPlanes(const mat4 &m) {
+ #ifdef _DEBUG
+ dbg = 0;
+ #endif
+ start = 0;
+ count = 5;
+ planes[0] = vec4(m.e30 - m.e20, m.e31 - m.e21, m.e32 - m.e22, m.e33 - m.e23); // near
+ planes[1] = vec4(m.e30 - m.e10, m.e31 - m.e11, m.e32 - m.e12, m.e33 - m.e13); // top
+ planes[2] = vec4(m.e30 - m.e00, m.e31 - m.e01, m.e32 - m.e02, m.e33 - m.e03); // right
+ planes[3] = vec4(m.e30 + m.e10, m.e31 + m.e11, m.e32 + m.e12, m.e33 + m.e13); // bottom
+ planes[4] = vec4(m.e30 + m.e00, m.e31 + m.e01, m.e32 + m.e02, m.e33 + m.e03); // left
+ for (int i = 0; i < count; i++)
+ planes[i] *= 1.0f / planes[i].xyz.length();
+ }
+
+ void calcPlanes(const Poly &poly) {
+ count = 1 + poly.count; // add one for near plane (not changing)
+ ASSERT(count < MAX_CLIP_PLANES);
+ if (count < 4) return;
+
+ vec3 e1 = poly.vertices[0] - pos;
+ for (int i = 1; i < count; i++) {
+ vec3 e2 = poly.vertices[i % poly.count] - pos;
+ planes[i].xyz = e1.cross(e2).normal();
+ planes[i].w = -(pos.dot(planes[i].xyz));
+ e1 = e2;
+ }
+ }
+
+ void clipPlane(const Poly &src, Poly &dst, const vec4 &plane) {
+ dst.count = 0;
+ if (!src.count) return;
+
+ float t1 = src.vertices[0].dot(plane.xyz) + plane.w;
+
+ for (int i = 0; i < src.count; i++) {
+ const vec3 &v1 = src.vertices[i];
+ const vec3 &v2 = src.vertices[(i + 1) % src.count];
+
+ float t2 = v2.dot(plane.xyz) + plane.w;
+
+ // hack for big float numbers
+ 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) { // check for opposite signs
+ float k1 = t2 / (t2 - t1);
+ float k2 = t1 / (t2 - t1);
+ dst.vertices[dst.count++] = v1 * (float)k1 - v2 * (float)k2;
+ ASSERT(dst.count < MAX_CLIP_PLANES);
+ }
+
+ t1 = t2;
+ }
+ }
+
+ bool clipByPortal(const vec3 *vertices, int vCount, const vec3 &normal) {
+ if (normal.dot(pos - vertices[0]) < 0.0f) // check portal winding order
+ return false;
+
+ Poly poly[2];
+
+ poly[0].count = vCount;
+ memmove(poly[0].vertices, vertices, sizeof(vec3) * poly[0].count);
+#ifdef _DEBUG
+ debugPoly.count = 0;
+#endif
+ int j = 0;
+ for (int i = 1; i < count; i++, j ^= 1)
+ clipPlane(poly[j], poly[j ^ 1], planes[i]);
+
+ calcPlanes(poly[j]);
+ return count >= 4;
+ }
+
+ // AABB visibility check
+ bool isVisible(const vec3 &min, const vec3 &max) const {
+ if (count < 4) return false;
+
+ for (int i = start; i < start + count; i++) {
+ const vec3 &n = planes[i].xyz;
+ const float d = -planes[i].w;
+
+ if (n.dot(max) < d &&
+ n.dot(min) < d &&
+ n.dot(vec3(min.x, max.y, max.z)) < d &&
+ n.dot(vec3(max.x, min.y, max.z)) < d &&
+ n.dot(vec3(min.x, min.y, max.z)) < d &&
+ n.dot(vec3(max.x, max.y, min.z)) < d &&
+ n.dot(vec3(min.x, max.y, min.z)) < d &&
+ n.dot(vec3(max.x, min.y, min.z)) < d)
+ return false;
+ }
+ return true;
+ }
+
+ // OBB visibility check
+ bool isVisible(const mat4 &matrix, const vec3 &min, const vec3 &max) {
+ start = count;
+ // transform clip planes (relative)
+ mat4 m = matrix.inverse();
+ for (int i = 0; i < count; i++) {
+ vec4 &p = planes[i];
+ vec4 o = m * vec4(p.xyz * (-p.w), 1.0f);
+ vec4 n = m * vec4(p.xyz, 0.0f);
+ planes[start + i] = vec4(n.xyz, -n.xyz.dot(o.xyz));
+ }
+ bool visible = isVisible(min, max);
+ start = 0;
+ return visible;
+ }
+
+ // Sphere visibility check
+ bool isVisible(const vec3 ¢er, float radius) {
+ if (count < 4) return false;
+
+ for (int i = 0; i < count; i++)
+ if (planes[i].xyz.dot(center) + planes[i].w < -radius)
+ return false;
+ return true;
+ }
+
+};
+
+#endif
\ No newline at end of file
diff --git a/src/lara.h b/src/lara.h
index 768d274..69676e9 100644
--- a/src/lara.h
+++ b/src/lara.h
@@ -165,7 +165,7 @@ struct Lara : Controller {
struct Weapon {
enum Type { EMPTY, PISTOLS, SHOTGUN, MAGNUMS, UZIS, MAX };
- enum State { IS_CLEAN, IS_ARMED, IS_FIRING };
+ enum State { IS_HIDDEN, IS_ARMED, IS_FIRING };
enum Anim { NONE, PREPARE, UNHOLSTER, HOLSTER, HOLD, AIM, FIRE };
int ammo; // if -1 weapon is not available
@@ -176,16 +176,20 @@ struct Lara : Controller {
Weapon::Anim wpnAnim;
float wpnAnimTime;
float wpnAnimDir;
- quat animOverrides[15]; // left & right arms animation frames
- int animOverrideMask;
+ int wpnLastFrame;
+ Weapon::Type wpnNext;
+ float wpnShotTime[2];
- Lara(TR::Level *level, int entity) : Controller(level, entity), wpnCurrent(Weapon::EMPTY), animOverrideMask(0) {
+ Lara(TR::Level *level, int entity) : Controller(level, entity), wpnCurrent(Weapon::EMPTY), wpnNext(Weapon::EMPTY) {
+ initMeshOverrides();
+ wpnShotTime[0] = wpnShotTime[1] = 0.0f;
memset(weapons, -1, sizeof(weapons));
weapons[Weapon::PISTOLS].ammo = 0;
- weapons[Weapon::SHOTGUN].ammo = 8;
-
- setWeapon(Weapon::PISTOLS, Weapon::IS_CLEAN, Weapon::Anim::NONE, 1.0f);
+ weapons[Weapon::SHOTGUN].ammo = 9000;
+ weapons[Weapon::MAGNUMS].ammo = 9000;
+ weapons[Weapon::UZIS ].ammo = 9000;
+ setWeapon(Weapon::PISTOLS, Weapon::IS_HIDDEN);
#ifdef _DEBUG
/*
// gym
@@ -212,7 +216,7 @@ struct Lara : Controller {
pos = vec3(31400, -2560, 25200);
angle = vec3(0.0f, PI, 0.0f);
getEntity().room = 43;
-
+
// level 2 (medikit)
pos = vec3(30800, -7936, 22131);
angle = vec3(0.0f, 0.0f, 0.0f);
@@ -237,13 +241,14 @@ struct Lara : Controller {
#endif
}
- void setWeapon(Weapon::Type wType, Weapon::State wState, Weapon::Anim wAnim, float wAnimDir) {
+ void setWeapon(Weapon::Type wType, Weapon::State wState, Weapon::Anim wAnim = Weapon::Anim::NONE, float wAnimDir = 0.0f) {
wpnAnimDir = wAnimDir;
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;
}
if (wpnCurrent == wType && wpnState == wState)
@@ -256,22 +261,30 @@ struct Lara : Controller {
case Weapon::MAGNUMS :
case Weapon::UZIS :
switch (wState) {
- case Weapon::IS_CLEAN : mask = BODY_LEG_L1 | BODY_LEG_R1; break;
+ case Weapon::IS_HIDDEN : mask = BODY_LEG_L1 | BODY_LEG_R1; break;
case Weapon::IS_ARMED : mask = BODY_ARM_L3 | BODY_ARM_R3; break;
case Weapon::IS_FIRING : mask = BODY_ARM_L3 | BODY_ARM_R3 | BODY_HEAD; break;
}
break;
case Weapon::SHOTGUN :
switch (wState) {
- case Weapon::IS_CLEAN : mask = BODY_CHEST; break;
+ case Weapon::IS_HIDDEN : mask = BODY_CHEST; break;
case Weapon::IS_ARMED : mask = BODY_ARM_L3 | BODY_ARM_R3; break;
case Weapon::IS_FIRING : mask = BODY_ARM_L3 | BODY_ARM_R3 | BODY_HEAD; break;
}
break;
+ default : ;
}
+ if (wpnState == Weapon::IS_HIDDEN && wState == Weapon::IS_ARMED) playSound(TR::SND_UNHOLSTER, pos, Sound::Flags::PAN);
+ 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)
+ resetMask &= ~(BODY_LEG_L1 | BODY_LEG_R1);
+
// restore original meshes first
- meshSwap(level->models[Weapon::EMPTY], BODY_HEAD | BODY_UPPER | BODY_LOWER);
+ meshSwap(level->models[Weapon::EMPTY], resetMask);
// replace some parts
meshSwap(level->models[wType], mask);
// have a shotgun in inventory place it on the back if another weapon is in use
@@ -285,8 +298,72 @@ struct Lara : Controller {
wpnState = wState;
}
+ bool emptyHands() {
+ return wpnState == Weapon::IS_HIDDEN;
+ }
+
+ bool canDrawWeapon() {
+ return wpnCurrent != Weapon::EMPTY
+ && state != STATE_DEATH
+ && state != STATE_HANG
+ && state != STATE_REACH
+ && state != STATE_TREAD
+ && state != STATE_SWIM
+ && state != STATE_GLIDE
+ && state != STATE_HANG_UP
+ && state != STATE_FALL_BACK
+ && state != STATE_HANG_LEFT
+ && state != STATE_HANG_RIGHT
+ && state != STATE_SURF_TREAD
+ && state != STATE_SURF_SWIM
+ && state != STATE_DIVE
+ && state != STATE_PUSH_BLOCK
+ && state != STATE_PULL_BLOCK
+ && state != STATE_PUSH_PULL_READY
+ && state != STATE_PICK_UP
+ && state != STATE_SWITCH_DOWN
+ && state != STATE_SWITCH_UP
+ && state != STATE_USE_KEY
+ && state != STATE_USE_PUZZLE
+ && state != STATE_UNDERWATER_DEATH
+ && state != STATE_SPECIAL
+ && state != STATE_SURF_BACK
+ && state != STATE_SURF_LEFT
+ && state != STATE_SURF_RIGHT
+ && state != STATE_SWAN_DIVE
+ && state != STATE_FAST_DIVE
+ && state != STATE_HANDSTAND
+ && state != STATE_WATER_OUT;
+ }
+
+ void drawWeapon() {
+ 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);
+ }
+ }
+
+ 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 changeWeapon(Weapon::Type wType) {
+ if (wpnCurrent == wType) return;
+ wpnNext = wType;
+ hideWeapon();
+ }
+
int getWeaponAnimIndex(Weapon::Anim wAnim) {
- int baseAnim = level->models[wpnCurrent].animation;
+ int baseAnim = level->models[wpnCurrent == Weapon::SHOTGUN ? Weapon::SHOTGUN : Weapon::PISTOLS].animation;
if (wpnCurrent == Weapon::SHOTGUN) {
switch (wAnim) {
@@ -296,6 +373,7 @@ struct Lara : Controller {
case Weapon::Anim::HOLD :
case Weapon::Anim::AIM : return baseAnim;
case Weapon::Anim::FIRE : return baseAnim + 2;
+ default : ;
}
} else
switch (wAnim) {
@@ -305,10 +383,202 @@ struct Lara : Controller {
case Weapon::Anim::HOLD :
case Weapon::Anim::AIM : return baseAnim;
case Weapon::Anim::FIRE : return baseAnim + 3;
+ default : ;
}
return 0;
}
+ int getWeaponSound() {
+ switch (wpnCurrent) {
+ case Weapon::PISTOLS : return TR::SND_PISTOLS_SHOT;
+ case Weapon::SHOTGUN : return TR::SND_SHOTGUN_SHOT;
+ case Weapon::MAGNUMS : return TR::SND_MAGNUMS_SHOT;
+ case Weapon::UZIS : return TR::SND_UZIS_SHOT;
+ default : return TR::SND_NO;
+ }
+ }
+
+ void doShot() {
+ playSound(getWeaponSound(), pos, Sound::Flags::PAN);
+
+ int count = wpnCurrent == Weapon::SHOTGUN ? 6 : 2;
+
+ float nearDist = 32.0f * 1024.0f;
+ vec3 nearPos;
+
+ 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);
+
+ vec3 t = p + d * (24.0f * 1024.0f) + ((vec3(randf(), randf(), randf()) * 2.0f) - 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);
+
+ float dist = (hit - p).length();
+ if (dist < nearDist) {
+ nearPos = hit;
+ nearDist = dist;
+ }
+ }
+
+ playSound(TR::SND_RICOCHET, nearPos, Sound::Flags::PAN);
+
+ wpnShotTime[0] = wpnShotTime[1] = 0.0f;
+ }
+
+ void updateWeapon() {
+ wpnShotTime[0] += Core::deltaTime;
+ wpnShotTime[1] += Core::deltaTime;
+
+ TR::Animation *anim = &level->anims[getWeaponAnimIndex(wpnAnim)];
+
+ 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();
+ 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();
+ else
+ hideWeapon();
+ }
+
+ 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;
+ }
+
+ anim = &level->anims[getWeaponAnimIndex(wpnAnim)];
+ float maxTime = (anim->frameEnd - anim->frameStart) / 30.0f;
+
+ 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);
+ 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;
+ default : ;
+ };
+ }
+
+ if (prevAnim != wpnAnim) // check by cache
+ anim = &level->anims[getWeaponAnimIndex(wpnAnim)];
+
+ // 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);
+ }
+ // shotgun reload sound
+ if (isRifle && frameIndex >= 10 && wpnLastFrame < 10)
+ playSound(TR::SND_SHOTGUN_RELOAD, pos, Sound::Flags::PAN);
+ }
+ wpnLastFrame = frameIndex;
+
+
+ if (wpnAnim == Weapon::Anim::NONE) {
+ animOverrideMask &= ~(BODY_ARM_L | BODY_ARM_R);
+ return;
+ }
+
+ // update animation overrides
+ float k = wpnAnimTime * 30.0f / anim->frameRate;
+ int fIndex = (int)k;
+ int fCount = (anim->frameEnd - anim->frameStart) / anim->frameRate + 1;
+
+ int fSize = sizeof(TR::AnimFrame) + getModel().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::AnimFrame *frameB = (TR::AnimFrame*)&level->frameData[(anim->frameOffset + fIndexB * fSize) >> 1];
+
+ // 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);
+ }
+
bool waterOut(int &outState) {
// TODO: playSound 36
vec3 dst = pos + getDir() * 32.0f;
@@ -411,14 +681,14 @@ struct Lara : Controller {
break;
case TR::Level::Trigger::SWITCH :
actionState = (isActive && stand == STAND_GROUND) ? STATE_SWITCH_UP : STATE_SWITCH_DOWN;
- if ((mask & ACTION) == 0 || state == actionState)
+ if ((mask & ACTION) == 0 || state == actionState || !emptyHands())
return;
if (!checkAngle(level->entities[info.trigCmd[0].args].rotation))
return;
break;
case TR::Level::Trigger::KEY :
actionState = STATE_USE_KEY;
- if (isActive || (mask & ACTION) == 0 || state == actionState) // TODO: STATE_USE_PUZZLE
+ if (isActive || (mask & ACTION) == 0 || state == actionState || !emptyHands()) // TODO: STATE_USE_PUZZLE
return;
if (!checkAngle(level->entities[info.trigCmd[0].args].rotation))
return;
@@ -511,6 +781,8 @@ struct Lara : Controller {
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 :
@@ -521,10 +793,6 @@ struct Lara : Controller {
return vec3(0.0f, h, 0.0f);
}
- bool emptyHands() {
- return wpnState == Weapon::IS_CLEAN;
- }
-
virtual Stand getStand() {
if (state == STATE_HANG || state == STATE_HANG_LEFT || state == STATE_HANG_RIGHT) {
if (mask & ACTION)
@@ -542,8 +810,10 @@ struct Lara : Controller {
if (stand == STAND_ONWATER && state != STATE_DIVE && state != STATE_STOP)
return stand;
- if (getRoom().flags.water)
- return STAND_UNDERWATER; // TODO: ONWATER
+ if (getRoom().flags.water) {
+ hideWeapon();
+ return STAND_UNDERWATER;
+ }
TR::Entity &e = getEntity();
TR::Level::FloorInfo info;
@@ -663,7 +933,7 @@ struct Lara : Controller {
virtual int getStateGround() {
angle.x = 0.0f;
- if ((mask & ACTION) && doPickUp() && emptyHands())
+ if ((mask & ACTION) && emptyHands() && doPickUp())
return STATE_PICK_UP;
if ( (mask & (FORTH | ACTION)) == (FORTH | ACTION) && (animIndex == ANIM_STAND || animIndex == ANIM_STAND_NORMAL) && emptyHands()) {
@@ -897,18 +1167,17 @@ struct Lara : Controller {
float rot = 0.0f;
#ifdef _DEBUG
- // show state transitions for current animation
+ // show state transitions for current animation
static bool lState = false;
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++);
+ static int snd_id = 0;
+ //playSound(snd_id, pos, 0);
+ 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];
@@ -919,7 +1188,7 @@ struct Lara : Controller {
}
LOG("\n");
}
-
+ */
}
} else
@@ -946,7 +1215,7 @@ struct Lara : Controller {
else if (state == STATE_TREAD || state == STATE_SURF_TREAD || state == STATE_SURF_SWIM || state == STATE_SURF_BACK)
w = TURN_WATER_SLOW;
else if (state == STATE_RUN || state == STATE_FAST_TURN)
- w = TURN_FAST;
+ w = TURN_FAST; // TODO: modulate angular speed by turnTime factor
else if (state == STATE_FAST_BACK)
w = TURN_FAST_BACK;
else if (state == STATE_TURN_LEFT || state == STATE_TURN_RIGHT || state == STATE_WALK)
@@ -1004,127 +1273,7 @@ struct Lara : Controller {
virtual void updateAnimation(bool commands) {
Controller::updateAnimation(commands);
-
- // 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) && wpnAnim != Weapon::Anim::PREPARE && wpnAnim != Weapon::Anim::UNHOLSTER && wpnAnim != Weapon::Anim::HOLSTER) {
- if (wpnState == Weapon::IS_CLEAN)
- setWeapon(wpnCurrent, wpnState, isRifle ? Weapon::Anim::UNHOLSTER : Weapon::Anim::PREPARE, 1.0f);
- else
- if (isRifle)
- setWeapon(wpnCurrent, wpnState, Weapon::Anim::HOLSTER, 1.0f);
- else
- setWeapon(wpnCurrent, wpnState, Weapon::Anim::UNHOLSTER, -1.0f);
- }
-
- if (wpnState != Weapon::IS_CLEAN) {
- 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;
- }
-
- TR::Animation *anim = &level->anims[getWeaponAnimIndex(wpnAnim)];
- float maxTime = (anim->frameEnd - anim->frameStart) / 30.0f;
-
- 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;
-
- /*
- case Weapon::Anim::FIRE :
- if (!(mask & ACTION))
- setWeapon(wpnCurrent, Weapon::IS_ARMED, Weapon::Anim::AIM, -1.0f);
- break;
- */
-
- if (wpnAnim == Weapon::Anim::FIRE && wpnAnimTime >= maxTime)
- if (!(mask & ACTION))
- setWeapon(wpnCurrent, Weapon::IS_ARMED, Weapon::Anim::AIM, -1.0f);
-
-
- 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);
- break;
- case Weapon::Anim::HOLSTER :
- if (wpnAnimTime >= maxTime)
- setWeapon(wpnCurrent, Weapon::IS_CLEAN, Weapon::Anim::NONE, wpnAnimDir);
- else if (wpnAnimTime >= maxTime * 0.7f)
- setWeapon(wpnCurrent, Weapon::IS_CLEAN, wpnAnim, 1.0f); break;
- case Weapon::Anim::AIM : if (wpnAnimTime >= maxTime) setWeapon(wpnCurrent, Weapon::IS_FIRING, Weapon::Anim::FIRE, wpnAnimDir); break;
- };
-
- if (wpnAnimDir < 0.0f && wpnAnimTime <= 0.0f)
- switch (wpnAnim) {
- case Weapon::Anim::AIM : setWeapon(wpnCurrent, wpnState, Weapon::Anim::HOLD, 0.0f); break;
- };
- } 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, wpnAnimDir); break;
- };
-
- 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_CLEAN, Weapon::Anim::PREPARE, wpnAnimDir); break;
- case Weapon::Anim::AIM : setWeapon(wpnCurrent, wpnState, Weapon::Anim::HOLD, 0.0f); break;
- };
- }
-
- if (prevAnim != wpnAnim) // check by cache
- anim = &level->anims[getWeaponAnimIndex(wpnAnim)];
-
- if (wpnAnim == Weapon::Anim::NONE) {
- animOverrideMask &= ~(BODY_ARM_L | BODY_ARM_R);
- return;
- }
-
- // update animation overrides
- float k = wpnAnimTime * 30.0f / anim->frameRate;
- int fIndex = (int)k;
- int fCount = (anim->frameEnd - anim->frameStart) / anim->frameRate + 1;
-
- int fSize = sizeof(TR::AnimFrame) + getModel().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::AnimFrame *frameB = (TR::AnimFrame*)&level->frameData[(anim->frameOffset + fIndexB * fSize) >> 1];
- LOG("%d %d %f\n", getWeaponAnimIndex(wpnAnim), fIndexA, wpnAnimTime);
-
- // 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);
+ updateWeapon();
}
quat lerpFrames(TR::AnimFrame *frameA, TR::AnimFrame *frameB, float t, int index) {
@@ -1385,6 +1534,29 @@ struct Lara : Controller {
updateEntity();
checkRoom();
}
+
+ void renderMuzzleFlash(MeshBuilder *mesh, const mat4 &matrix, const vec3 &offset, float time) {
+ if (time > 0.1f) 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);
+ 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;
+ }
+
+ virtual void render(Frustum *frustum, MeshBuilder *mesh) {
+ Controller::render(frustum, mesh);
+ if (wpnCurrent != Weapon::SHOTGUN) {
+ renderMuzzleFlash(mesh, joints[10], vec3(-10, -50, 150), wpnShotTime[0]);
+ renderMuzzleFlash(mesh, joints[13], vec3( 10, -50, 150), wpnShotTime[1]);
+ }
+ }
};
#endif
\ No newline at end of file
diff --git a/src/level.h b/src/level.h
index 25a9353..d10a685 100644
--- a/src/level.h
+++ b/src/level.h
@@ -40,7 +40,7 @@ struct Level {
initShaders();
initOverrides();
- for (int i = 0; i < level.entitiesCount; i++) {
+ for (int i = 0; i < level.entitiesBaseCount; i++) {
TR::Entity &entity = level.entities[i];
switch (entity.type) {
case TR::Entity::LARA :
@@ -104,7 +104,11 @@ struct Level {
case TR::Entity::HOLE_KEY :
entity.controller = new Trigger(&level, i, false);
break;
- default : ;
+ default :
+ if (entity.modelIndex > 0)
+ entity.controller = new Controller(&level, i);
+ else
+ entity.controller = new SpriteController(&level, i, 0);
}
}
@@ -261,7 +265,8 @@ struct Level {
mat4 mTemp = Core::mModel;
Core::mModel.translate(offset);
Core::mModel.rotateY(rMesh.rotation);
- renderMesh(sMesh->mesh);
+ Core::active.shader->setParam(uModel, Core::mModel);
+ mesh->renderMesh(mesh->meshMap[sMesh->mesh]);
Core::mModel = mTemp;
}
}
@@ -324,141 +329,6 @@ struct Level {
camera->frustum = camFrustum; // pop camera frustum
}
- void renderMesh(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 renderShadow(const vec3 &pos, const vec3 &offset, const vec3 &size, float angle) {
- mat4 m;
- m.identity();
- m.translate(pos);
- m.rotateY(angle);
- m.translate(vec3(offset.x, 0.0f, offset.z));
- m.scale(vec3(size.x, 0.0f, size.z) * (1.0f / 1024.0f));
-
- Core::active.shader->setParam(uModel, m);
- Core::active.shader->setParam(uColor, vec4(0.0f, 0.0f, 0.0f, 0.5f));
- mesh->renderShadowSpot();
- }
-
- void renderModel(const TR::Model &model, const TR::Entity &entity) {
- TR::Animation *anim;
- float fTime;
- vec3 angle;
-
- Controller *controller = (Controller*)entity.controller;
-
- if (controller) {
- anim = &level.anims[controller->animIndex];
- angle = controller->angle;
- fTime = controller->animTime;
- } else {
- anim = &level.anims[model.animation];
- angle = vec3(0.0f, entity.rotation, 0.0f);
- fTime = time;
- }
-
- 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;
-
- 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);
- vec3 bmax = frameA->box.max().lerp(frameB->box.max(), k);
- if (!camera->frustum->isVisible(Core::mModel, bmin, bmax))
- return;
-
- 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));
-
- 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) m = stack[--sIndex];
- if (t.flags & 0x02) stack[sIndex++] = m;
-
- ASSERT(sIndex >= 0 && sIndex < 20);
-
- m.translate(vec3(t.x, t.y, t.z));
- }
-
- quat q;
- if (entity.type == TR::Entity::LARA && (((Lara*)controller)->animOverrideMask & (1 << i)))
- q = ((Lara*)controller)->animOverrides[i];
- else
- q = lerpAngle(frameA->getAngle(i), frameB->getAngle(i), k);
- m = m * mat4(q, vec3(0.0f));
-
-
- // vec3 angle = lerpAngle(getAngle(frameA, i), getAngle(frameB, i), k);
- // m.rotateY(angle.y);
- // m.rotateX(angle.x);
- // m.rotateZ(angle.z);
-
- mat4 tmp = Core::mModel;
- Core::mModel = Core::mModel * m;
- if (controller)
- renderMesh(controller->meshes[i]);
- else
- renderMesh(model.mStart + i);
- Core::mModel = tmp;
- }
-
- if (TR::castShadow(entity.type)) {
- TR::Level::FloorInfo info;
- level.getFloorInfo(entity.room, entity.x, entity.z, info, true);
- renderShadow(vec3(entity.x, info.floor - 16.0f, entity.z), (bmax + bmin) * 0.5f, (bmax - bmin) * 0.8f, entity.rotation);
- }
- }
-
- void renderSequence(const TR::Entity &entity) {
- shaders[shSprite]->bind();
- Core::active.shader->setParam(uModel, Core::mModel);
- Core::active.shader->setParam(uColor, Core::color);
-
- int sIndex = -(entity.modelIndex + 1);
- int sFrame;
- if (entity.controller)
- sFrame = ((SpriteController*)entity.controller)->frame;
- else
- sFrame = int(time * 10.0f) % level.spriteSequences[sIndex].sCount;
- mesh->renderSprite(sIndex, sFrame);
- }
-
int getLightIndex(const vec3 &pos, int &room) {
int idx = -1;
float dist;
@@ -498,6 +368,7 @@ struct Level {
void renderEntity(const TR::Entity &entity) {
if (entity.type == TR::Entity::NONE) return;
+ ASSERT(entity.controller);
TR::Room &room = level.rooms[entity.room];
if (!room.flags.rendered || entity.flags.invisible) // check for room visibility
@@ -509,31 +380,28 @@ struct Level {
float c = (entity.intensity > -1) ? (1.0f - entity.intensity / (float)0x1FFF) : 1.0f;
float l = 1.0f;
- // set shader
- setRoomShader(room, c)->bind();
+ if (entity.modelIndex > 0) { // model
+ // set shader
+ setRoomShader(room, c)->bind();
+ Core::active.shader->setParam(uColor, Core::color);
+ // get light parameters for entity
+ getLight(vec3(entity.x, entity.y, entity.z), entity.room);
+ }
+
+ if (entity.modelIndex < 0) { // sprite
+ shaders[shSprite]->bind();
+ Core::color = vec4(c, c, c, 1.0f);
+ }
Core::active.shader->setParam(uColor, Core::color);
- // get light parameters for entity
- getLight(vec3(entity.x, entity.y, entity.z), entity.room);
-
- // render entity models
- if (entity.modelIndex > 0) {
- PROFILE_MARKER("MDL");
- renderModel(level.models[entity.modelIndex - 1], entity);
- }
- // if entity is billboard
- if (entity.modelIndex < 0) {
- PROFILE_MARKER("SPR");
- Core::color = vec4(c, c, c, 1.0f);
- renderSequence(entity);
- }
+ ((Controller*)entity.controller)->render(camera->frustum, mesh);
Core::mModel = m;
}
void update() {
time += Core::deltaTime;
-
+
for (int i = 0; i < level.entitiesCount; i++)
if (level.entities[i].type != TR::Entity::NONE) {
Controller *controller = (Controller*)level.entities[i].controller;
diff --git a/src/platform/web/index.html b/src/platform/web/index.html
index 974340e..d9f5488 100644
--- a/src/platform/web/index.html
+++ b/src/platform/web/index.html
@@ -5,7 +5,7 @@
Starting...
- OpenLara on github
(controls -> gamepad or keyboad: move - WASD / arrows, jump - Space, action - E/Ctrl, walk - Shift, side steps - ZX/walk+direction, camera - MouseR)
+ OpenLara on github
controls:
keyboad: move - WASD / arrows, jump - Space, action - E/Ctrl, draw weapon - Q, change weapon - 1-4, walk - Shift, side steps - ZX/walk+direction, camera - MouseR)
gamepad: PSX controls on XBox controller