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