mirror of
https://github.com/XProger/OpenLara.git
synced 2025-08-09 06:36:59 +02:00
#7 draw/hide weapons (hand animations and mesh swap), shotting, muzzle flash, tracing bullets for walls
#8 combat mode, underwater sound
This commit is contained in:
BIN
bin/OpenLara.exe
BIN
bin/OpenLara.exe
Binary file not shown.
230
src/camera.h
230
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();
|
||||
|
233
src/controller.h
233
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
|
39
src/format.h
39
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 {
|
||||
|
152
src/frustum.h
Normal file
152
src/frustum.h
Normal file
@@ -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
|
474
src/lara.h
474
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
|
178
src/level.h
178
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;
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<span id="status">Starting...</span>
|
||||
<input type="button" value="fullscreen" onclick="Module.requestFullScreen(false, true)"><br><br>
|
||||
<canvas id="canvas" width="160" height="120" oncontextmenu="event.preventDefault()"></canvas><br>
|
||||
<span id="info"><a target="_blank" href="https://github.com/XProger/OpenLara">OpenLara on github</a><br>(controls -> gamepad or keyboad: move - WASD / arrows, jump - Space, action - E/Ctrl, walk - Shift, side steps - ZX/walk+direction, camera - MouseR)</span>
|
||||
<span id="info"><a target="_blank" href="https://github.com/XProger/OpenLara">OpenLara on github</a><br>controls:<br>keyboad: move - WASD / arrows, jump - Space, action - E/Ctrl, draw weapon - Q, change weapon - 1-4, walk - Shift, side steps - ZX/walk+direction, camera - MouseR)<br>gamepad: PSX controls on XBox controller</span>
|
||||
|
||||
<script type='text/javascript'>
|
||||
var statusElement = document.getElementById('status');
|
||||
|
@@ -156,6 +156,7 @@
|
||||
<ClInclude Include="..\..\core.h" />
|
||||
<ClInclude Include="..\..\debug.h" />
|
||||
<ClInclude Include="..\..\enemy.h" />
|
||||
<ClInclude Include="..\..\frustum.h" />
|
||||
<ClInclude Include="..\..\game.h" />
|
||||
<ClInclude Include="..\..\input.h" />
|
||||
<ClInclude Include="..\..\lara.h" />
|
||||
|
@@ -313,7 +313,7 @@ int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi
|
||||
|
||||
float slow = Input::down[ikR] ? 8.0f : 1.0f;
|
||||
|
||||
float delta = (time - lastTime) * 0.001f;
|
||||
float delta = min(1.0f, (time - lastTime) * 0.001f);
|
||||
EnterCriticalSection(&sndCS);
|
||||
while (delta > EPS) {
|
||||
Core::deltaTime = min(delta, 1.0f / 30.0f) / slow;
|
||||
|
11
src/utils.h
11
src/utils.h
@@ -32,6 +32,7 @@
|
||||
#define DEG2RAD (PI / 180.0f)
|
||||
#define RAD2DEG (180.0f / PI)
|
||||
#define EPS FLT_EPSILON
|
||||
#define randf() ((float)rand()/RAND_MAX)
|
||||
|
||||
typedef char int8;
|
||||
typedef short int16;
|
||||
@@ -507,6 +508,16 @@ quat lerpAngle(const vec3 &a, const vec3 &b, float t) { // TODO: optimization
|
||||
return ma.getRot().slerp(mb.getRot(), t).normal();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
struct Box {
|
||||
vec3 min, max;
|
||||
|
||||
|
Reference in New Issue
Block a user