1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-01-17 21:09:00 +01:00

code refactoring; animation, character and sprite classes, compiler warnings etc.; fixed #19; fixed #20

This commit is contained in:
XProger 2016-11-20 23:27:42 +03:00
parent 7f004415e1
commit 251f99339b
18 changed files with 989 additions and 994 deletions

Binary file not shown.

253
src/animation.h Normal file
View File

@ -0,0 +1,253 @@
#ifndef H_ANIMATION
#define H_ANIMATION
#include "utils.h"
#include "format.h"
struct Animation {
TR::Level *level;
TR::Model *model;
TR::Animation *anims;
int state;
float time, timeMax, delta, dir;
int index, prev, next;
int frameIndex, framePrev, framesCount;
TR::AnimFrame *frameA, *frameB;
vec3 offset, jump;
bool isEnded, isPrepareToNext, flip;
quat *overrides; // left & right arms animation frames
int overrideMask;
Animation() : overrides(NULL) {}
Animation(TR::Level *level, TR::Model *model) : level(level), model(model), anims(model ? &level->anims[model->animation] : NULL), time(0), delta(0), dir(1.0f),
index(-1), prev(0), next(0), overrides(NULL), overrideMask(0) {
if (anims) setAnim(0);
}
~Animation() {
delete[] overrides;
}
inline operator TR::Animation* () const { return anims + index; }
void initOverrides() {
overrides = new quat[model->mCount];
overrideMask = 0;
}
void update() {
if (!isEnded) {
time += dir * Core::deltaTime;
isEnded = time <= 0.0f || time >= timeMax;
time = clamp(time, 0.0f, timeMax - EPS);
}
updateInfo();
}
int setAnim(int animIndex, int animFrame = 0, bool lerpToNext = true) {
TR::Animation *anim = anims + animIndex;
isEnded = isPrepareToNext = false;
offset = jump = vec3(0.0f);
prev = index;
index = animIndex;
next = anims[index].nextAnimation - model->animation;
time = (animFrame <= 0 ? -animFrame : (animFrame - anim->frameStart)) / 30.0f;
timeMax = (anim->frameEnd - anim->frameStart + lerpToNext) / 30.0f;
framesCount = anim->frameEnd - anim->frameStart + 1;
updateInfo();
framePrev = frameIndex - 1;
getCommand(anim, frameIndex, &offset, &jump, NULL);
return state = anim->state;
}
void playNext() {
setAnim(next, anims[index].nextFrame);
}
void updateInfo() {
ASSERT(model);
ASSERT(anims);
TR::Animation *anim = anims + index;
// framePrev = frameIndex;
frameIndex = int(time * 30.0f);
// get count of real frames
int fCount = (anim->frameEnd - anim->frameStart) / anim->frameRate + 1;
// real frame index & lerp delta
int fIndex = int(time * 30.0f) / anim->frameRate;
int k = fIndex * anim->frameRate;
delta = (time * 30.0f - k) / min((int)anim->frameRate, framesCount - k); // min is because in some cases framesCount > realFramesCount / frameRate * frameRate
// size of frame (in bytes)
int fSize = sizeof(TR::AnimFrame) + model->mCount * sizeof(uint16) * 2;
int fIndexA = fIndex % fCount, fIndexB = (fIndex + 1) % fCount;
frameA = (TR::AnimFrame*)&level->frameData[(anim->frameOffset + fIndexA * fSize) >> 1]; // >> 1 (div 2) because frameData is array of shorts
int frameNext = frameIndex + 1;
isPrepareToNext = !fIndexB;
if (isPrepareToNext) {
frameNext = anim->nextFrame;
anim = &level->anims[anim->nextAnimation];
frameNext -= anim->frameStart;
fIndexB = frameNext / anim->frameRate;
}
getCommand(anim, frameNext, NULL, NULL, &flip);
frameB = (TR::AnimFrame*)&level->frameData[(anim->frameOffset + fIndexB * fSize) >> 1];
}
bool isFrameActive(int index) {
return index > framePrev && index <= frameIndex;
}
bool canSetState(int state) {
TR::Animation *anim = anims + index;
if (state == anim->state)
return true;
for (int i = 0; i < anim->scCount; i++) {
TR::AnimState &s = level->states[anim->scOffset + i];
if (s.state == state)
for (int j = 0; j < s.rangesCount; j++) {
TR::AnimRange &range = level->ranges[s.rangesOffset + j];
if (anim->frameStart + frameIndex >= range.low && anim->frameStart + frameIndex <= range.high)
return true;
}
}
return false;
}
bool setState(int state) {
TR::Animation *anim = anims + index;
if (state == anim->state)
return true;
bool exists = false;
for (int i = 0; i < anim->scCount; i++) {
TR::AnimState &s = level->states[anim->scOffset + i];
if (s.state == state) {
exists = true;
for (int j = 0; j < s.rangesCount; j++) {
TR::AnimRange &range = level->ranges[s.rangesOffset + j];
if (anim->frameStart + frameIndex >= range.low && anim->frameStart + frameIndex <= range.high) {
setAnim(range.nextAnimation - model->animation, range.nextFrame);
break;
}
}
}
}
return exists;
}
float getSpeed() {
TR::Animation *anim = anims + index;
return anim->speed + anim->accel * (time * 30.0f);
}
void getCommand(TR::Animation *anim, int frameIndex, vec3 *offset, vec3 *jump, bool *flip) {
int16 *ptr = &level->commands[anim->animCommand];
if (offset) *offset = vec3(0.0f);
if (flip) *flip = false;
for (int i = 0; i < anim->acCount; i++) {
int cmd = *ptr++;
switch (cmd) {
case TR::ANIM_CMD_OFFSET :
if (offset) {
offset->x = (float)*ptr++;
offset->y = (float)*ptr++;
offset->z = (float)*ptr++;
} else
ptr += 3;
break;
case TR::ANIM_CMD_JUMP :
if (jump) {
jump->y = (float)*ptr++;
jump->z = (float)*ptr++;
} else
ptr += 2;
break;
case TR::ANIM_CMD_SOUND : ptr += 2; break;
case TR::ANIM_CMD_EFFECT :
if (flip) {
int frame = (*ptr++) - anim->frameStart;
int fx = (*ptr++) & 0x3FFF;
*flip = fx == TR::EFFECT_ROTATE_180 && frame == frameIndex;
} else
ptr += 2;
break;
}
}
}
quat getJointRot(int joint) {
return lerpAngle(frameA->getAngle(joint), frameB->getAngle(joint), delta);
}
mat4 getJoints(mat4 matrix, int joint, bool postRot = false, mat4 *joints = NULL) {
TR::Animation *anim = anims + index;
vec3 offset = isPrepareToNext ? this->offset : vec3(0.0f);
matrix.translate(((vec3)frameA->pos).lerp(offset + frameB->pos, delta));
TR::Node *node = (int)model->node < level->nodesDataSize ? (TR::Node*)&level->nodesData[model->node] : NULL;
int sIndex = 0;
mat4 stack[16];
for (int i = 0; i < model->mCount; i++) {
if (i > 0 && node) {
TR::Node &t = node[i - 1];
if (t.flags & 0x01) matrix = stack[--sIndex];
if (t.flags & 0x02) stack[sIndex++] = matrix;
ASSERT(sIndex >= 0 && sIndex < 16);
matrix.translate(vec3((float)t.x, (float)t.y, (float)t.z));
}
if (i == joint && !postRot)
return matrix;
quat q;
if (overrideMask & (1 << i))
q = overrides[i];
else
q = getJointRot(i);
matrix = matrix * mat4(q, vec3(0.0f));
if (i == joint && postRot)
return matrix;
if (joints)
joints[i] = matrix;
}
return matrix;
}
Box getBoundingBox(const vec3 &pos, int dir) {
vec3 min = frameA->box.min().lerp(frameB->box.min(), delta);
vec3 max = frameA->box.max().lerp(frameB->box.max(), delta);
Box box(min, max);
box.rotate90(dir);
box.min += pos;
box.max += pos;
return box;
}
};
#endif

View File

@ -56,7 +56,9 @@ struct Camera : Controller {
}
virtual void update() {
actTargetEntity = owner->target;
int lookAt = -1;
if (owner->target > -1) lookAt = owner->target;
if (actTargetEntity > -1) lookAt = actTargetEntity;
if (timer > 0.0f) {
timer -= Core::deltaTime;
@ -65,6 +67,7 @@ struct Camera : Controller {
if (room != getRoomIndex())
pos = lastDest;
actTargetEntity = actCamera = -1;
target = owner->getViewPoint();
}
}
#ifdef FREE_CAMERA
@ -91,28 +94,28 @@ struct Camera : Controller {
angle.z = 0.0f;
//angle.x = min(max(angle.x, -80 * DEG2RAD), 80 * DEG2RAD);
float lerpFactor = (actTargetEntity == -1) ? 6.0f : 10.0f;
float lerpFactor = (lookAt == -1) ? 6.0f : 10.0f;
vec3 dir;
target = target.lerp(owner->getViewPoint(), lerpFactor * Core::deltaTime);
if (actCamera > -1) {
TR::Camera &c = level->cameras[actCamera];
destPos = vec3(c.x, c.y, c.z);
destPos = vec3(float(c.x), float(c.y), float(c.z));
if (room != getRoomIndex())
pos = destPos;
if (actTargetEntity > -1) {
TR::Entity &e = level->entities[actTargetEntity];
target = vec3(e.x, e.y, e.z);
if (lookAt > -1) {
TR::Entity &e = level->entities[lookAt];
target = ((Controller*)e.controller)->pos;
}
} else {
if (actTargetEntity > -1) {
TR::Entity &e = level->entities[actTargetEntity];
dir = (vec3(e.x, e.y, e.z) - target).normal();
if (lookAt > -1) {
TR::Entity &e = level->entities[lookAt];
dir = (((Controller*)e.controller)->pos - target).normal();
} else
dir = getDir();
int destRoom;
if ((owner->wpnState != Lara::Weapon::IS_HIDDEN || owner->state != Lara::STATE_BACK_JUMP) || actTargetEntity > -1) {
if ((!owner->emptyHands() || owner->state != Lara::STATE_BACK_JUMP) || lookAt > -1) {
vec3 eye = target - dir * CAMERA_OFFSET;
destPos = trace(owner->getRoomIndex(), target, eye, destRoom, true);
lastDest = destPos;
@ -138,7 +141,7 @@ struct Camera : Controller {
room = info.roomAbove;
else
if (info.ceiling != 0xffff8100)
pos.y = info.ceiling;
pos.y = (float)info.ceiling;
}
if (pos.y > info.floor) {
@ -146,7 +149,7 @@ struct Camera : Controller {
room = info.roomBelow;
else
if (info.floor != 0xffff8100)
pos.y = info.floor;
pos.y = (float)info.floor;
}
// play underwater sound when camera goes under water

137
src/character.h Normal file
View File

@ -0,0 +1,137 @@
#ifndef H_CHARACTER
#define H_CHARACTER
#include "controller.h"
struct Character : Controller {
int target;
int health;
float tilt;
quat rotHead, rotChest;
enum Stand {
STAND_AIR, STAND_GROUND, STAND_SLIDE, STAND_HANG, STAND_UNDERWATER, STAND_ONWATER
} stand;
int input;
enum { LEFT = 1 << 1,
RIGHT = 1 << 2,
FORTH = 1 << 3,
BACK = 1 << 4,
JUMP = 1 << 5,
WALK = 1 << 6,
ACTION = 1 << 7,
WEAPON = 1 << 8,
DEATH = 1 << 9 };
vec3 velocity;
float angleExt;
Character(TR::Level *level, int entity, int health) : Controller(level, entity), target(-1), health(100), tilt(0.0f), stand(STAND_GROUND), velocity(0.0f) {
animation.initOverrides();
rotHead = rotChest = quat(0, 0, 0, 1);
}
virtual void hit(int damage) {
health -= damage;
};
virtual void checkRoom() {
TR::Level::FloorInfo info;
TR::Entity &e = getEntity();
level->getFloorInfo(e.room, e.x, e.z, info);
if (info.roomNext != 0xFF)
e.room = info.roomNext;
if (info.roomBelow != 0xFF && e.y > info.floor)
e.room = info.roomBelow;
if (info.roomAbove != 0xFF && e.y <= info.ceiling) {
if (stand == STAND_UNDERWATER && !level->rooms[info.roomAbove].flags.water) {
stand = STAND_ONWATER;
velocity.y = 0;
pos.y = float(info.ceiling);
updateEntity();
} else
if (stand != STAND_ONWATER)
e.room = info.roomAbove;
}
}
virtual void cmdKill() {
health = 0;
}
virtual void updatePosition() {}
virtual Stand getStand() { return stand; }
virtual int getHeight() { return 0; }
virtual int getStateAir() { return state; }
virtual int getStateGround() { return state; }
virtual int getStateSlide() { return state; }
virtual int getStateHang() { return state; }
virtual int getStateUnderwater() { return state; }
virtual int getStateOnwater() { return state; }
virtual int getStateDeath() { return state; }
virtual int getStateDefault() { return state; }
virtual int getInput() { return 0; }
virtual void updateState() {
int state = animation.state;
if (input & DEATH)
state = getStateDeath();
else if (stand == STAND_GROUND)
state = getStateGround();
else if (stand == STAND_SLIDE)
state = getStateSlide();
else if (stand == STAND_HANG)
state = getStateHang();
else if (stand == STAND_AIR)
state = getStateAir();
else if (stand == STAND_UNDERWATER)
state = getStateUnderwater();
else
state = getStateOnwater();
// try to set new state
if (!animation.setState(state))
animation.setState(getStateDefault());
}
virtual void updateTilt(bool active, float tiltSpeed, float tiltMax) {
// calculate turning tilt
if (active && (input & (LEFT | RIGHT)) && (tilt == 0.0f || (tilt < 0.0f && (input & LEFT)) || (tilt > 0.0f && (input & RIGHT)))) {
if (input & LEFT) tilt -= tiltSpeed * Core::deltaTime;
if (input & RIGHT) tilt += tiltSpeed * Core::deltaTime;
} else
if (fabsf(tilt) > 0.01f) {
if (tilt > 0.0f)
tilt -= min(tilt, tiltSpeed * 4.0f * Core::deltaTime);
else
tilt -= max(tilt, -tiltSpeed * 4.0f * Core::deltaTime);
} else
tilt = 0.0f;
tilt = clamp(tilt, -tiltMax, tiltMax);
angle.z = tilt;
}
virtual void update() {
input = getInput();
stand = getStand();
updateState();
Controller::update();
updateVelocity();
updatePosition();
}
virtual void cmdJump(const vec3 &vel) {
velocity.x = sinf(angleExt) * vel.z;
velocity.y = vel.y;
velocity.z = cosf(angleExt) * vel.z;
stand = STAND_AIR;
}
};
#endif

View File

@ -4,51 +4,25 @@
#include "format.h"
#include "frustum.h"
#include "mesh.h"
#include "animation.h"
#define GRAVITY 6.0f
#define NO_OVERLAP 0x7FFFFFFF
#define SPRITE_FPS 10.0f
struct Controller {
TR::Level *level;
int entity;
Animation animation;
int &state;
enum Stand {
STAND_AIR, STAND_GROUND, STAND_SLIDE, STAND_HANG, STAND_UNDERWATER, STAND_ONWATER
} stand;
int state;
int mask;
enum { LEFT = 1 << 1,
RIGHT = 1 << 2,
FORTH = 1 << 3,
BACK = 1 << 4,
JUMP = 1 << 5,
WALK = 1 << 6,
ACTION = 1 << 7,
WEAPON = 1 << 8,
DEATH = 1 << 9 };
float animTime;
int animIndex, animPrev;
int animPrevFrame;
vec3 pos, velocity;
vec3 pos;
vec3 angle;
float angleExt;
int *meshes;
int mCount;
// TODO: Character class
quat *animOverrides; // left & right arms animation frames
int animOverrideMask;
mat4 *joints;
int health;
float tilt;
struct ActionCommand {
TR::Action action;
int value;
@ -59,145 +33,39 @@ 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), actionCommand(NULL), mCount(0), meshes(NULL), animOverrides(NULL), animOverrideMask(0), joints(NULL) {
Controller(TR::Level *level, int entity) : level(level), entity(entity), animation(level, getModel()), state(animation.state), actionCommand(NULL), mCount(0), meshes(NULL) {
TR::Entity &e = getEntity();
pos = vec3((float)e.x, (float)e.y, (float)e.z);
angle = vec3(0.0f, e.rotation, 0.0f);
stand = STAND_GROUND;
animIndex = e.modelIndex > 0 ? getModel().animation : 0;
animPrev = animIndex;
state = level->anims[animIndex].state;
TR::Model &model = getModel();
health = 100;
tilt = 0.0f;
}
virtual ~Controller() {
delete[] meshes;
delete[] animOverrides;
delete[] joints;
}
void initMeshOverrides() {
TR::Model &model = getModel();
mCount = model.mCount;
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;
meshes[i] = model->mStart + i;
}
void initAnimOverrides() {
TR::Model &model = getModel();
animOverrides = new quat[model.mCount];
animOverrideMask = 0;
joints = new mat4[model.mCount];
}
void meshSwap(TR::Model &model, int mask) {
for (int i = 0; i < model.mCount; i++) {
int index = model.mStart + i;
void meshSwap(TR::Model *model, int mask) {
for (int i = 0; i < model->mCount; i++) {
int index = model->mStart + i;
if (((1 << i) & mask) && level->meshOffsets[index])
meshes[i] = index;
}
}
int getFramesCount(int animIndex) {
TR::Animation &anim = level->anims[animIndex];
return (anim.frameEnd - anim.frameStart) / anim.frameRate + 1;
}
int getFrameIndex(int animIndex, float t) {
TR::Animation &anim = level->anims[animIndex];
return int(t * 30.0f / anim.frameRate) % ((anim.frameEnd - anim.frameStart) / anim.frameRate + 1);
}
void getFrames(TR::AnimFrame **frameA, TR::AnimFrame **frameB, float &t, int animIndex, float animTime, bool nextAnim = false, vec3 *move = NULL) {
TR::Animation *anim = &level->anims[animIndex];
t = animTime * 30.0f / anim->frameRate;
int fIndex = (int)t;
int fCount = (anim->frameEnd - anim->frameStart) / anim->frameRate + 1;
int fSize = sizeof(TR::AnimFrame) + getModel().mCount * sizeof(uint16) * 2;
t -= fIndex;
int fIndexA = fIndex % fCount, fIndexB = (fIndex + 1) % fCount;
*frameA = (TR::AnimFrame*)&level->frameData[(anim->frameOffset + fIndexA * fSize) >> 1];
if (!fIndexB) {
if (move)
*move = getAnimMove();
if (nextAnim) {
int nextFrame = anim->nextFrame;
anim = &level->anims[anim->nextAnimation];
fIndexB = (nextFrame - anim->frameStart) / anim->frameRate;
}
}
*frameB = (TR::AnimFrame*)&level->frameData[(anim->frameOffset + fIndexB * fSize) >> 1];
}
mat4 getJoint(int index, bool postRot = false) {
mat4 matrix;
matrix.identity();
matrix.translate(pos);
if (angle.y != 0.0f) matrix.rotateY(angle.y);
if (angle.x != 0.0f) matrix.rotateX(angle.x);
if (angle.z != 0.0f) matrix.rotateZ(angle.z);
TR::Animation *anim = &level->anims[animIndex];
TR::Model &model = getModel();
float t;
vec3 move(0.0f);
TR::AnimFrame *frameA, *frameB;
getFrames(&frameA, &frameB, t, animIndex, animTime, true, &move);
TR::Node *node = (int)model.node < level->nodesDataSize ? (TR::Node*)&level->nodesData[model.node] : NULL;
matrix.translate(((vec3)frameA->pos).lerp(move + frameB->pos, t));
int sIndex = 0;
mat4 stack[20];
for (int i = 0; i < model.mCount; i++) {
if (i > 0 && node) {
TR::Node &t = node[i - 1];
if (t.flags & 0x01) matrix = stack[--sIndex];
if (t.flags & 0x02) stack[sIndex++] = matrix;
ASSERT(sIndex >= 0 && sIndex < 20);
matrix.translate(vec3(t.x, t.y, t.z));
}
if (i == index && !postRot)
return matrix;
quat q;
if (animOverrideMask & (1 << i))
q = animOverrides[i];
else
q = lerpAngle(frameA->getAngle(i), frameB->getAngle(i), t);
matrix = matrix * mat4(q, vec3(0.0f));
if (i == index && postRot)
return matrix;
}
return matrix;
}
bool aim(int target, int joint, const vec4 &angleRange, quat &rot, quat *rotAbs = NULL) {
if (target > -1) {
TR::Entity &e = level->entities[target];
Box box = ((Controller*)e.controller)->getBoundingBox();
vec3 t = (box.min + box.max) * 0.5f;
mat4 m = getJoint(joint);
mat4 m = animation.getJoints(getMatrix(), joint);
vec3 delta = (m.inverse() * t).normal();
float angleY = clampAngle(atan2(delta.x, delta.z));
@ -233,16 +101,17 @@ struct Controller {
bool insideRoom(const vec3 &pos, int room) const {
TR::Room &r = level->rooms[room];
vec3 min = vec3(r.info.x, r.info.yTop, r.info.z);
vec3 max = min + vec3(r.xSectors * 1024, r.info.yBottom - r.info.yTop, r.zSectors * 1024);
vec3 min = vec3((float)r.info.x, (float)r.info.yTop, (float)r.info.z);
vec3 max = min + vec3(float(r.xSectors * 1024), float(r.info.yBottom - r.info.yTop), float(r.zSectors * 1024));
return pos.x >= min.x && pos.x <= max.x &&
pos.y >= min.y && pos.y <= max.y &&
pos.z >= min.z && pos.z <= max.z;
}
TR::Model& getModel() const {
return level->models[getEntity().modelIndex - 1];
TR::Model* getModel() const {
int index = getEntity().modelIndex;
return index > 0 ? &level->models[index - 1] : NULL;
}
TR::Entity& getEntity() const {
@ -259,64 +128,6 @@ struct Controller {
return getEntity().room;
}
int setAnimation(int index, int frame = 0) {
animPrev = animIndex;
animIndex = index;
TR::Animation &anim = level->anims[animIndex];
animTime = (frame <= 0 ? -frame : (frame - anim.frameStart)) / 30.0f;
ASSERT(anim.frameStart <= anim.frameEnd);
animPrevFrame = int(animTime * 30.0f) - 1;
return state = anim.state;
}
bool canSetState(int state) {
TR::Animation *anim = &level->anims[animIndex];
if (state == anim->state)
return true;
int fIndex = int(animTime * 30.0f);
for (int i = 0; i < anim->scCount; i++) {
TR::AnimState &s = level->states[anim->scOffset + i];
if (s.state == state)
for (int j = 0; j < s.rangesCount; j++) {
TR::AnimRange &range = level->ranges[s.rangesOffset + j];
if (anim->frameStart + fIndex >= range.low && anim->frameStart + fIndex <= range.high)
return true;
}
}
return false;
}
bool setState(int state) {
TR::Animation *anim = &level->anims[animIndex];
if (state == anim->state)
return true;
int fIndex = int(animTime * 30.0f);
bool exists = false;
for (int i = 0; i < anim->scCount; i++) {
TR::AnimState &s = level->states[anim->scOffset + i];
if (s.state == state) {
exists = true;
for (int j = 0; j < s.rangesCount; j++) {
TR::AnimRange &range = level->ranges[s.rangesOffset + j];
if (anim->frameStart + fIndex >= range.low && anim->frameStart + fIndex <= range.high) {
setAnimation(range.nextAnimation, range.nextFrame);
break;
}
}
}
}
return exists;
}
int getOverlap(int fromX, int fromY, int fromZ, int toX, int toZ) const {
int dx, dz;
TR::Room::Sector &s = level->getSector(getEntity().room, fromX, fromZ, dx, dz);
@ -391,15 +202,7 @@ struct Controller {
}
virtual Box getBoundingBox() {
float t;
TR::AnimFrame *frameA, *frameB;
getFrames(&frameA, &frameB, t, animIndex, animTime, true);
Box box(frameA->box.min().lerp(frameB->box.min(), t), frameA->box.max().lerp(frameB->box.max(), t));
box.rotate90(getEntity().rotation.value / 0x4000);
box.min += pos;
box.max += pos;
return box;
return animation.getBoundingBox(pos, getEntity().rotation.value / 0x4000);
}
vec3 trace(int fromRoom, const vec3 &from, const vec3 &to, int &room, bool isCamera) { // TODO: use Bresenham
@ -439,7 +242,7 @@ struct Controller {
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;
pos = vec3(float(clamp(px, minX, maxX)), pos.y, float(clamp(pz, minZ, maxZ))) + boxNormal(px, pz) * 256.0f;
dir = (pos - from).normal();
}
} else {
@ -472,7 +275,7 @@ struct Controller {
if (rand() % 10 <= 6) return;
playSound(TR::SND_BUBBLE, pos, Sound::Flags::PAN);
}
/*
void collide() {
TR::Entity &entity = getEntity();
@ -510,7 +313,7 @@ struct Controller {
}
}
}
*/
void activateNext() { // activate next entity (for triggers)
if (!actionCommand || !actionCommand->next) {
actionCommand = NULL;
@ -555,93 +358,27 @@ struct Controller {
actionCommand = NULL;
}
virtual bool activate(ActionCommand *cmd) { actionCommand = cmd; return true; }
virtual void doCustomCommand (int curFrame, int prevFrame) {}
virtual void updateVelocity() {}
virtual void checkRoom() {}
virtual void move() {}
virtual Stand getStand() { return STAND_AIR; }
virtual int getHeight() { return 0; }
virtual int getStateAir() { return state; }
virtual int getStateGround() { return state; }
virtual int getStateSlide() { return state; }
virtual int getStateHang() { return state; }
virtual int getStateUnderwater() { return state; }
virtual int getStateOnwater() { return state; }
virtual int getStateDeath() { return state; }
virtual int getStateDefault() { return state; }
virtual int getInputMask() { return 0; }
virtual void hit(int damage) { };
virtual bool activate(ActionCommand *cmd) { actionCommand = cmd; return true; }
virtual void doCustomCommand (int curFrame, int prevFrame) {}
virtual void updateVelocity() {}
virtual void checkRoom() {}
virtual int getState(Stand stand) {
TR::Animation *anim = &level->anims[animIndex];
int state = anim->state;
if (mask & DEATH)
state = getStateDeath();
else if (stand == STAND_GROUND)
state = getStateGround();
else if (stand == STAND_SLIDE)
state = getStateSlide();
else if (stand == STAND_HANG)
state = getStateHang();
else if (stand == STAND_AIR)
state = getStateAir();
else if (stand == STAND_UNDERWATER)
state = getStateUnderwater();
else
state = getStateOnwater();
// try to set new state
if (!setState(state))
setState(getStateDefault());
return level->anims[animIndex].state;
}
virtual void updateBegin() {
mask = getInputMask();
state = getState(stand = getStand());
}
virtual void updateEnd() {
TR::Entity &e = getEntity();
move();
virtual void cmdOffset(const vec3 &offset) {
pos = pos + offset.rotateY(-angle.y);
updateEntity();
checkRoom();
}
virtual void updateState() {}
virtual void cmdJump(const vec3 &vel) {}
virtual void cmdKill() {}
virtual void cmdEmpty() {}
virtual vec3 getAnimMove() {
TR::Animation *anim = &level->anims[animIndex];
int16 *ptr = &level->commands[anim->animCommand];
for (int i = 0; i < anim->acCount; i++) {
int cmd = *ptr++;
switch (cmd) {
case TR::ANIM_CMD_MOVE : { // cmd position
int16 sx = *ptr++;
int16 sy = *ptr++;
int16 sz = *ptr++;
return vec3((float)sx, (float)sy, (float)sz);
break;
}
case TR::ANIM_CMD_SPEED : // cmd jump speed
case TR::ANIM_CMD_SOUND : // play sound
case TR::ANIM_CMD_EFFECT : // special commands
ptr += 2;
break;
}
}
return vec3(0.0f);
}
virtual void updateAnimation(bool commands) {
int frameIndex = int((animTime += Core::deltaTime) * 30.0f);
TR::Animation *anim = &level->anims[animIndex];
bool endFrame = frameIndex > anim->frameEnd - anim->frameStart;
animation.update();
TR::Animation *anim = animation;
// apply animation commands
if (commands) {
int16 *ptr = &level->commands[anim->animCommand];
@ -649,50 +386,24 @@ struct Controller {
for (int i = 0; i < anim->acCount; i++) {
int cmd = *ptr++;
switch (cmd) {
case TR::ANIM_CMD_MOVE : { // cmd position
int16 sx = *ptr++;
int16 sy = *ptr++;
int16 sz = *ptr++;
if (endFrame) {
pos = pos + vec3(sx, sy, sz).rotateY(-angle.y);
updateEntity();
checkRoom();
LOG("move: %d %d %d\n", (int)sx, (int)sy, (int)sz);
}
break;
}
case TR::ANIM_CMD_SPEED : { // cmd jump speed
int16 sy = *ptr++;
int16 sz = *ptr++;
if (endFrame) {
LOG("jump: %d %d\n", (int)sy, (int)sz);
velocity.x = sinf(angleExt) * sz;
velocity.y = sy;
velocity.z = cosf(angleExt) * sz;
stand = STAND_AIR;
}
break;
}
case TR::ANIM_CMD_EMPTY : // empty hands
break;
case TR::ANIM_CMD_KILL : // kill
break;
case TR::ANIM_CMD_SOUND : // play sound
case TR::ANIM_CMD_EFFECT : { // special commands
int frame = (*ptr++);
int id = (*ptr++) & 0x3FFF;
int idx = frame - anim->frameStart;
if (idx > animPrevFrame && idx <= frameIndex) {
case TR::ANIM_CMD_OFFSET : ptr += 3; break;
case TR::ANIM_CMD_JUMP : ptr += 2; break;
case TR::ANIM_CMD_EMPTY : cmdEmpty(); break;
case TR::ANIM_CMD_KILL : cmdKill(); break;
case TR::ANIM_CMD_SOUND :
case TR::ANIM_CMD_EFFECT : {
int frame = (*ptr++) - anim->frameStart;
int fx = (*ptr++) & 0x3FFF;
if (animation.isFrameActive(frame)) {
if (cmd == TR::ANIM_CMD_EFFECT) {
switch (id) {
case TR::EFFECT_ROTATE_180 : angle.y = angle.y + PI; break;
switch (fx) {
case TR::EFFECT_ROTATE_180 : angle.y = angle.y + PI; 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);
default : LOG("unknown special cmd %d (anim %d)\n", fx, animation.index);
}
} else
playSound(id, pos, Sound::Flags::PAN);
playSound(fx, pos, Sound::Flags::PAN);
}
break;
}
@ -700,21 +411,20 @@ struct Controller {
}
}
doCustomCommand(frameIndex, animPrevFrame);
if (animation.frameIndex != animation.framePrev)
doCustomCommand(animation.frameIndex, animation.framePrev);
if (endFrame) { // if animation is end - switch to next
setAnimation(anim->nextAnimation, anim->nextFrame);
if (animation.isEnded) { // if animation is end - switch to next
if (animation.offset != 0.0f) cmdOffset(animation.offset);
if (animation.jump != 0.0f) cmdJump(animation.jump);
animation.playNext();
activateNext();
} else
animPrevFrame = frameIndex;
animation.framePrev = animation.frameIndex;
}
virtual void update() {
updateBegin();
updateState();
updateAnimation(true);
updateVelocity();
updateEnd();
updateAnimation(true);
}
void renderMesh(const mat4 &matrix, MeshBuilder *mesh, uint32 offsetIndex) {
@ -739,139 +449,40 @@ struct Controller {
mesh->renderShadowSpot();
}
virtual void render(Frustum *frustum, MeshBuilder *mesh) {
TR::Entity &entity = getEntity();
TR::Model &model = getModel();
TR::Animation *anim = &level->anims[animIndex];
mat4 matrix(Core::mModel);
mat4 getMatrix() {
mat4 matrix;
matrix.identity();
matrix.translate(pos);
if (angle.y != 0.0f) matrix.rotateY(angle.y);
if (angle.y != 0.0f) matrix.rotateY(angle.y - (animation.flip ? PI * animation.delta : 0.0f));
if (angle.x != 0.0f) matrix.rotateX(angle.x);
if (angle.z != 0.0f) matrix.rotateZ(angle.z);
return matrix;
}
float t;
vec3 move(0.0f);
TR::AnimFrame *frameA, *frameB;
getFrames(&frameA, &frameB, t, animIndex, animTime, true, &move);
virtual void render(Frustum *frustum, MeshBuilder *mesh) { // TODO: animation.calcJoints
mat4 matrix = getMatrix();
vec3 bmin = frameA->box.min().lerp(frameB->box.min(), t);
vec3 bmax = frameA->box.max().lerp(frameB->box.max(), t);
if (frustum && !frustum->isVisible(matrix, bmin, bmax))
Box box = animation.getBoundingBox(vec3(0, 0, 0), 0);
if (frustum && !frustum->isVisible(matrix, box.min, box.max))
return;
TR::Entity &entity = getEntity();
TR::Model *model = getModel();
entity.flags.rendered = true;
TR::Node *node = (int)model.node < level->nodesDataSize ? (TR::Node*)&level->nodesData[model.node] : NULL;
mat4 joints[32]; // TODO: UBO heap
ASSERT(model->mCount <= 32);
matrix.translate(((vec3)frameA->pos).lerp(move + frameB->pos, t));
int sIndex = 0;
mat4 stack[20];
for (int i = 0; i < model.mCount; i++) {
if (i > 0 && node) {
TR::Node &t = node[i - 1];
if (t.flags & 0x01) matrix = stack[--sIndex];
if (t.flags & 0x02) stack[sIndex++] = matrix;
ASSERT(sIndex >= 0 && sIndex < 20);
matrix.translate(vec3(t.x, t.y, t.z));
}
quat q;
if (animOverrideMask & (1 << i))
q = animOverrides[i];
else
q = lerpAngle(frameA->getAngle(i), frameB->getAngle(i), t);
matrix = matrix * mat4(q, vec3(0.0f));
if (meshes)
renderMesh(matrix, mesh, meshes[i]);
else
renderMesh(matrix, mesh, model.mStart + i);
if (joints)
joints[i] = matrix;
}
animation.getJoints(matrix, -1, true, joints);
for (int i = 0; i < model->mCount; i++)
renderMesh(joints[i], mesh, meshes ? meshes[i] : (model->mStart + i));
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);
renderShadow(mesh, vec3(float(entity.x), info.floor - 16.0f, float(entity.z)), box.center(), box.size() * 0.8f, angle.y);
}
}
quat lerpFrames(TR::AnimFrame *frameA, TR::AnimFrame *frameB, float t, int index) {
return lerpAngle(frameA->getAngle(index), frameB->getAngle(index), t);
}
};
struct SpriteController : Controller {
enum {
FRAME_ANIMATED = -1,
FRAME_RANDOM = -2,
};
int frame, flag;
bool instant;
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
this->frame = rand() % getSequence().sCount;
} else if (frame == FRAME_ANIMATED) { // animated
this->frame = 0;
}
}
TR::SpriteSequence& getSequence() {
return level->spriteSequences[-(getEntity().modelIndex + 1)];
}
void update() {
if (flag >= 0) return;
bool remove = false;
animTime += Core::deltaTime;
if (flag == FRAME_ANIMATED) {
frame = int(animTime * SPRITE_FPS);
TR::SpriteSequence &seq = getSequence();
if (instant && frame >= seq.sCount)
remove = true;
else
frame %= seq.sCount;
} else
if (instant && animTime >= (1.0f / SPRITE_FPS))
remove = true;
if (remove) {
level->entityRemove(entity);
delete this;
}
}
virtual void render(Frustum *frustum, MeshBuilder *mesh) {
mat4 m(Core::mModel);
m.translate(pos);
Core::active.shader->setParam(uModel, m);
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) {
int index = level->entityAdd(type, room, x, y, z, 0, -1);
if (index > -1) {
level->entities[index].intensity = 0x1FFF - level->rooms[room].ambient;
level->entities[index].controller = new SpriteController(level, index, true, frame);
}
}
#endif

View File

@ -449,13 +449,13 @@ namespace Debug {
TR::Node *node = m.node < level.nodesDataSize ? (TR::Node*)&level.nodesData[m.node] : NULL;
if (!node) continue; // ???
/*
if (e.type == m.type) {
ASSERT(m.animation < 0xFFFF);
int fSize = sizeof(TR::AnimFrame) + m.mCount * sizeof(uint16) * 2;
TR::Animation *anim = controller ? &level.anims[controller->animIndex] : &level.anims[m.animation];
TR::Animation *anim = controller->animation;
TR::AnimFrame *frame = (TR::AnimFrame*)&level.frameData[(anim->frameOffset + (controller ? int((controller->animTime * 30.0f / anim->frameRate)) * fSize : 0) >> 1)];
//mat4 m;
@ -495,35 +495,39 @@ namespace Debug {
int offset = level.meshOffsets[m.mStart + k];
TR::Mesh *mesh = (TR::Mesh*)&level.meshData[offset / 2];
Debug::Draw::sphere(matrix * joint * mesh->center, mesh->collider.radius, mesh->collider.info ? vec4(1, 0, 0, 0.5f) : vec4(0, 1, 1, 0.5f));
/*
{ //if (e.id != 0) {
char buf[255];
sprintf(buf, "(%d) radius %d info %d flags %d", e.id, (int)mesh->collider.radius, (int)mesh->collider.info, (int)mesh->collider.flags);
Debug::Draw::text(matrix * joint * mesh->center, vec4(0.5, 1, 0.5, 1), buf);
}
*/
}
Debug::Draw::box(matrix, frame->box.min(), frame->box.max(), vec4(1.0));
break;
}
*/
}
}
}
void info(const TR::Level &level, const TR::Entity &entity, int state, int anim, int frame) {
void info(const TR::Level &level, const TR::Entity &entity, Animation &anim) {
char buf[255];
sprintf(buf, "DIP = %d, TRI = %d, SND = %d", Core::stats.dips, Core::stats.tris, Sound::channelsCount);
Debug::Draw::text(vec2(16, 16), vec4(1.0f), buf);
sprintf(buf, "pos = (%d, %d, %d), room = %d, state = %d, anim = %d, frame = %d", entity.x, entity.y, entity.z, entity.room, state, anim, frame);
sprintf(buf, "pos = (%d, %d, %d), room = %d", entity.x, entity.y, entity.z, entity.room);
Debug::Draw::text(vec2(16, 32), vec4(1.0f), buf);
int rate = anim.anims[anim.index].frameRate;
sprintf(buf, "state = %d, anim = %d, next = %d, rate = %d, frame = %.2f / %d (%f)", anim.state, anim.index, anim.next, rate, anim.time * 30.0f, anim.framesCount, anim.delta);
Debug::Draw::text(vec2(16, 48), vec4(1.0f), buf);
TR::Level::FloorInfo info;
level.getFloorInfo(entity.room, entity.x, entity.z, info);
sprintf(buf, "floor = %d, roomBelow = %d, roomAbove = %d, height = %d", info.floorIndex, info.roomBelow, info.roomAbove, info.floor - info.ceiling);
Debug::Draw::text(vec2(16, 48), vec4(1.0f), buf);
Debug::Draw::text(vec2(16, 64), vec4(1.0f), buf);
}
}
}

View File

@ -1,26 +1,11 @@
#ifndef H_ENEMY
#define H_ENEMY
#include "controller.h"
#include "character.h"
struct Enemy : Controller {
int target;
quat rotHead, rotChest;
int baseAnim;
struct Enemy : Character {
Enemy(TR::Level *level, int entity) : Controller(level, entity), target(-1) {
initAnimOverrides();
rotHead = rotChest = quat(0, 0, 0, 1);
baseAnim = animIndex;
}
virtual Stand getStand() {
return STAND_GROUND;
}
virtual void hit(int damage) {
health -= damage;
};
Enemy(TR::Level *level, int entity, int health) : Character(level, entity, health) {}
virtual bool activate(ActionCommand *cmd) {
Controller::activate(cmd);
@ -38,66 +23,40 @@ struct Enemy : Controller {
}
virtual void updateVelocity() {
TR::Animation *anim = &level->anims[animIndex];
float speed = anim->speed + anim->accel * (animTime * 30.0f);
TR::Animation *anim = animation;
float speed = anim->speed + anim->accel * (animation.time * 30.0f);
velocity = getDir() * speed;
}
virtual void move() {
virtual void updatePosition() {
if (!getEntity().flags.active) return;
vec3 p = pos;
pos += velocity * Core::deltaTime * 30.0f;
TR::Level::FloorInfo info;
level->getFloorInfo(getRoomIndex(), (int)pos.x, (int)pos.z, info);
level->getFloorInfo(getRoomIndex(), (int)pos.x, (int)pos.z, info, true);
if (pos.y - info.floor > 1024) {
pos = p;
return;
}
if (stand == STAND_GROUND)
pos.y = info.floor;
pos.y = float(info.floor);
updateEntity();
checkRoom();
}
virtual void checkRoom() {
TR::Level::FloorInfo info;
TR::Entity &e = getEntity();
level->getFloorInfo(e.room, e.x, e.z, info);
if (info.roomNext != 0xFF)
e.room = info.roomNext;
if (info.roomBelow != 0xFF && e.y > info.floor)
e.room = info.roomBelow;
if (info.roomAbove != 0xFF && e.y <= info.ceiling) {
if (stand == STAND_UNDERWATER && !level->rooms[info.roomAbove].flags.water) {
stand = STAND_ONWATER;
velocity.y = 0;
pos.y = info.ceiling;
updateEntity();
} else
if (stand != STAND_ONWATER)
e.room = info.roomAbove;
}
}
void setOverrides(bool active, int chest, int head) {
int mask = 0;
if (head > -1) mask |= (1 << head);
if (chest > -1) mask |= (1 << chest);
if (active && head > -1) {
animation.overrides[head] = animation.getJointRot(head);
animation.overrideMask |= (1 << head);
} else
animation.overrideMask &= ~(1 << head);
if (active)
animOverrideMask |= mask;
else
animOverrideMask &= ~mask;
TR::AnimFrame *frameA, *frameB;
float t;
getFrames(&frameA, &frameB, t, animIndex, animTime, true);
animOverrides[chest] = lerpFrames(frameA, frameB, t, chest);
animOverrides[head] = lerpFrames(frameA, frameB, t, head);
if (active && chest > -1) {
animation.overrides[chest] = animation.getJointRot(chest);
animation.overrideMask |= (1 << chest);
} else
animation.overrideMask &= ~(1 << chest);
}
void lookAt(int target, int chest, int head) {
@ -105,11 +64,11 @@ struct Enemy : Controller {
quat rot;
if (chest > -1) {
if (aim(target, chest, vec4(-PI * 0.4f, PI * 0.4f, -PI * 0.75f, PI * 0.75f), rot))
if (aim(target, chest, vec4(-PI * 0.8f, PI * 0.8f, -PI * 0.75f, PI * 0.75f), rot))
rotChest = rotChest.slerp(quat(0, 0, 0, 1).slerp(rot, 0.5f), speed);
else
rotChest = rotChest.slerp(quat(0, 0, 0, 1), speed);
animOverrides[chest] = rotChest * animOverrides[chest];
animation.overrides[chest] = rotChest * animation.overrides[chest];
}
if (head > -1) {
@ -117,14 +76,16 @@ struct Enemy : Controller {
rotHead = rotHead.slerp(rot, speed);
else
rotHead = rotHead.slerp(quat(0, 0, 0, 1), speed);
animOverrides[head] = rotHead * animOverrides[head];
animation.overrides[head] = rotHead * animation.overrides[head];
}
}
virtual int getInputMask() {
virtual int getInput() {
if (target > -1) {
vec3 v = (((Controller*)level->entities[target].controller)->pos - pos).normal();
float d = atan2(v.x, v.z) - angle.y;
vec3 a = (((Controller*)level->entities[target].controller)->pos - pos).normal();
vec3 b = getDir();
vec3 n = vec3(0, 1, 0);
float d = atan2(b.cross(a).dot(n), a.dot(b));
if (fabsf(d) > 0.01f)
return d < 0 ? LEFT : RIGHT;
}
@ -135,6 +96,9 @@ struct Enemy : Controller {
#define WOLF_TURN_FAST PI
#define WOLF_TURN_SLOW (PI / 3.0f)
#define WOLF_TILT_MAX (PI / 6.0f)
#define WOLF_TILT_SPEED WOLF_TILT_MAX
struct Wolf : Enemy {
@ -163,7 +127,7 @@ struct Wolf : Enemy {
JOINT_HEAD = 3
};
Wolf(TR::Level *level, int entity) : Enemy(level, entity) {}
Wolf(TR::Level *level, int entity) : Enemy(level, entity, 100) {}
virtual int getStateGround() {
// STATE_SLEEP -> STATE_STOP
@ -178,9 +142,9 @@ struct Wolf : Enemy {
if (health <= 0) {
switch (state) {
case STATE_RUN : return setAnimation(baseAnim + ANIM_DEATH_RUN);
case STATE_JUMP : return setAnimation(baseAnim + ANIM_DEATH_JUMP);
default : return setAnimation(baseAnim + ANIM_DEATH);
case STATE_RUN : return animation.setAnim(ANIM_DEATH_RUN);
case STATE_JUMP : return animation.setAnim(ANIM_DEATH_JUMP);
default : return animation.setAnim(ANIM_DEATH);
}
}
@ -191,7 +155,7 @@ struct Wolf : Enemy {
switch (state) {
case STATE_SLEEP : return STATE_STOP;
case STATE_STOP : return STATE_HOWL;
case STATE_GROWL : return STATE_STALKING;
case STATE_GROWL : return randf() > 0.5f ? STATE_STALKING : STATE_RUN;
case STATE_STALKING : if (health < 70) return STATE_RUN; break;
}
@ -215,7 +179,7 @@ struct Wolf : Enemy {
float w = 0.0f;
if (state == STATE_RUN || state == STATE_STALKING) {
w = state == STATE_RUN ? WOLF_TURN_FAST : WOLF_TURN_SLOW;
if (mask & LEFT) w = -w;
if (input & LEFT) w = -w;
if (w != 0.0f) {
w *= Core::deltaTime;
@ -226,12 +190,14 @@ struct Wolf : Enemy {
velocity = vec3(0.0f);
}
virtual void move() {
virtual void updatePosition() {
updateTilt(state == STATE_RUN, WOLF_TILT_SPEED, WOLF_TILT_MAX);
if (state == STATE_DEATH) {
animOverrideMask = 0;
animation.overrideMask = 0;
return;
}
Enemy::move();
Enemy::updatePosition();
setOverrides(state == STATE_STALKING || state == STATE_RUN, JOINT_CHEST, JOINT_HEAD);
lookAt(target, JOINT_CHEST, JOINT_HEAD);
}
@ -244,7 +210,7 @@ struct Bear : Enemy {
STATE_STOP = 1,
};
Bear(TR::Level *level, int entity) : Enemy(level, entity) {}
Bear(TR::Level *level, int entity) : Enemy(level, entity, 100) {}
virtual int getStateGround() {
return state;
@ -259,14 +225,14 @@ struct Bat : Enemy {
STATE_FLY = 2,
};
Bat(TR::Level *level, int entity) : Enemy(level, entity) {}
Bat(TR::Level *level, int entity) : Enemy(level, entity, 100) {}
virtual Stand getStand() {
return STAND_AIR;
}
virtual int getStateAir() {
animTime = 0.0f;
animation.time = 0.0f;
return STATE_AWAKE;
}
};

View File

@ -5,13 +5,14 @@
#define MAX_RESERVED_ENTITIES 64
#define MAX_SECRETS_COUNT 16
#define MAX_TRIGGER_COMMANDS 32
namespace TR {
enum {
ANIM_CMD_NONE ,
ANIM_CMD_MOVE ,
ANIM_CMD_SPEED ,
ANIM_CMD_OFFSET ,
ANIM_CMD_JUMP ,
ANIM_CMD_EMPTY ,
ANIM_CMD_KILL ,
ANIM_CMD_SOUND ,
@ -106,6 +107,15 @@ namespace TR {
SND_SECRET = 173,
};
enum {
MODEL_LARA = 0,
MODEL_PISTOLS = 1,
MODEL_SHOTGUN = 2,
MODEL_MAGNUMS = 3,
MODEL_UZIS = 4,
MODEL_LARA_SPEC = 5,
};
enum Action : uint16 {
ACTIVATE , // activate item
CAMERA_SWITCH , // switch to camera
@ -419,6 +429,17 @@ namespace TR {
bool isEnemy() {
return type >= ENEMY_TWIN && type <= ENEMY_LARSON;
}
int isItem() {
return (type >= WEAPON_PISTOLS && type <= AMMO_UZIS) ||
(type >= PUZZLE_1 && type <= PUZZLE_4) ||
(type >= KEY_1 && type <= KEY_4) ||
(type == MEDIKIT_SMALL || type == MEDIKIT_BIG || type == ARTIFACT || type == PICKUP);
}
bool isBlock() {
return type >= TR::Entity::BLOCK_1 && type <= TR::Entity::BLOCK_2;
}
};
struct Animation {
@ -463,11 +484,13 @@ namespace TR {
vec3 max() const { return vec3((float)maxX, (float)maxY, (float)maxZ); }
};
#pragma warning( push )
#pragma warning( disable : 4200 ) // zero-sized array warning
struct AnimFrame {
MinMax box;
Vertex pos; // Starting offset for this model
Vertex pos;
int16 aCount;
uint16 angles[0]; // angle frames in YXZ order
uint16 angles[0]; // angle frames in YXZ order
vec3 getAngle(int index) {
#define ANGLE_SCALE (2.0f * PI / 1024.0f)
@ -475,7 +498,7 @@ namespace TR {
uint16 b = angles[index * 2 + 0];
uint16 a = angles[index * 2 + 1];
return vec3((a & 0x3FF0) >> 4, ( ((a & 0x000F) << 6) | ((b & 0xFC00) >> 10)), b & 0x03FF) * ANGLE_SCALE;
return vec3(float((a & 0x3FF0) >> 4), float( ((a & 0x000F) << 6) | ((b & 0xFC00) >> 10)), float(b & 0x03FF)) * ANGLE_SCALE;
}
};
@ -483,6 +506,7 @@ namespace TR {
int16 count; // number of texture offsets - 1 in group
int16 textures[0]; // offsets into objectTextures[]
};
#pragma warning( push )
struct Node {
uint32 flags;
@ -715,7 +739,7 @@ namespace TR {
int trigCmdCount;
Trigger trigger;
FloorData::TriggerInfo trigInfo;
FloorData::TriggerCommand trigCmd[16];
FloorData::TriggerCommand trigCmd[MAX_TRIGGER_COMMANDS];
vec3 getNormal() {
return vec3((float)-slantX, -4.0f, (float)-slantZ).normal();
@ -734,7 +758,8 @@ namespace TR {
bool secrets[MAX_SECRETS_COUNT];
void *cameraController;
Level(Stream &stream, bool demo) {
Level(const char *name, bool demo) {
Stream stream(name);
// read version
stream.read(version);
// tiles
@ -1039,6 +1064,7 @@ namespace TR {
info.trigInfo = (*fd++).triggerInfo;
FloorData::TriggerCommand trigCmd;
do {
ASSERT(info.trigCmdCount < MAX_TRIGGER_COMMANDS);
trigCmd = (*fd++).triggerCmd; // trigger action
info.trigCmd[info.trigCmdCount++] = trigCmd;
} while (!trigCmd.end);

View File

@ -12,8 +12,9 @@ namespace Game {
void init() {
Core::init();
Stream stream("LEVEL2_DEMO.PHD");
level = new Level(stream, true);
level = new Level("LEVEL2_DEMO.PHD", true, false);
//level = new Level("GYM.PHD", false, true);
//level = new Level("LEVEL4.PHD", false, false);
#ifndef __EMSCRIPTEN__
//Sound::play(Sound::openWAD("05_Lara's_Themes.wav"), 1, 1, 0);
@ -29,7 +30,13 @@ namespace Game {
}
void update() {
float dt = Core::deltaTime;
if (Input::down[ikR]) // slow motion (for animation debugging)
Core::deltaTime /= 50.0f;
level->update();
Core::deltaTime = dt;
}
void render() {

File diff suppressed because it is too large Load Diff

View File

@ -30,7 +30,7 @@ struct Level {
float time;
Level(Stream &stream, bool demo) : level{stream, demo}, time(0.0f), lara(NULL) {
Level(const char *name, bool demo, bool home) : level(name, demo), time(0.0f), lara(NULL) {
#ifdef _DEBUG
Debug::init();
#endif
@ -44,8 +44,10 @@ struct Level {
TR::Entity &entity = level.entities[i];
switch (entity.type) {
case TR::Entity::LARA :
entity.controller = (lara = new Lara(&level, i, home));
break;
case TR::Entity::LARA_CUT :
entity.controller = (lara = new Lara(&level, i));
entity.controller = (lara = new Lara(&level, i, false));
break;
case TR::Entity::ENEMY_WOLF :
entity.controller = new Wolf(&level, i);
@ -71,7 +73,7 @@ struct Level {
case TR::Entity::ENEMY_CENTAUR :
case TR::Entity::ENEMY_MUMMY :
case TR::Entity::ENEMY_LARSON :
entity.controller = new Enemy(&level, i);
entity.controller = new Enemy(&level, i, 100);
break;
case TR::Entity::DOOR_1 :
case TR::Entity::DOOR_2 :
@ -108,7 +110,7 @@ struct Level {
if (entity.modelIndex > 0)
entity.controller = new Controller(&level, i);
else
entity.controller = new SpriteController(&level, i, 0);
entity.controller = new Sprite(&level, i, 0);
}
}
@ -232,7 +234,7 @@ struct Level {
PROFILE_MARKER("ROOM");
TR::Room &room = level.rooms[roomIndex];
vec3 offset = vec3(room.info.x, 0.0f, room.info.z);
vec3 offset = vec3(float(room.info.x), 0.0f, float(room.info.z));
Shader *sh = setRoomShader(room, 1.0f);
@ -251,9 +253,11 @@ struct Level {
TR::StaticMesh *sMesh = level.getMeshByID(rMesh.meshID);
ASSERT(sMesh != NULL);
if (!mesh->meshMap[sMesh->mesh]) continue;
// check visibility
Box box;
vec3 offset = vec3(rMesh.x, rMesh.y, rMesh.z);
vec3 offset = vec3((float)rMesh.x, (float)rMesh.y, (float)rMesh.z);
sMesh->getBox(false, rMesh.rotation, box);
if (!camera->frustum->isVisible(offset + box.min, offset + box.max))
continue;
@ -343,7 +347,7 @@ struct Level {
int j = room;
for (int i = 0; i < level.rooms[j].lightsCount; i++) {
TR::Room::Light &light = level.rooms[j].lights[i];
float d = (pos - vec3(light.x, light.y, light.z)).length2();
float d = (pos - vec3(float(light.x), float(light.y), float(light.z))).length2();
if (idx == -1 || d < dist) {
idx = i;
dist = d;
@ -360,7 +364,7 @@ struct Level {
if (idx > -1) {
TR::Room::Light &light = level.rooms[room].lights[idx];
float c = level.rooms[room].lights[idx].intensity / 8191.0f;
Core::lightPos[0] = vec3(light.x, light.y, light.z);
Core::lightPos[0] = vec3(float(light.x), float(light.y), float(light.z));
Core::lightColor[0] = vec4(c, c, c, (float)light.attenuation * (float)light.attenuation);
} else {
Core::lightPos[0] = vec3(0);
@ -389,7 +393,7 @@ struct Level {
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);
getLight(((Controller*)entity.controller)->pos, entity.room);
}
if (entity.modelIndex < 0) { // sprite
@ -482,7 +486,7 @@ struct Level {
// Debug::Level::portals(level);
// Debug::Level::meshes(level);
// Debug::Level::entities(level);
Debug::Level::info(level, lara->getEntity(), (int)lara->state, lara->animIndex, int(lara->animTime * 30.0f));
Debug::Level::info(level, lara->getEntity(), lara->animation);
Debug::end();
#endif
}

View File

@ -2504,16 +2504,16 @@ int mp3_decode_init() {
for(i=0; i<512*16; i++){
int exponent= (i>>4);
double f= libc_pow(i&15, 4.0 / 3.0) * libc_pow(2, (exponent-400)*0.25 + FRAC_BITS + 5);
expval_table[exponent][i&15]= f;
expval_table[exponent][i&15]= uint32_t(f);
if((i&15)==1)
exp_table[exponent]= f;
exp_table[exponent]= uint32_t(f);
}
for(i=0;i<7;i++) {
float f;
int v;
if (i != 6) {
f = tan((double)i * M_PI / 12.0);
f = float(tan((double)i * M_PI / 12.0));
v = FIXR(f / (1.0 + f));
} else {
v = FIXR(1.0);
@ -2522,7 +2522,7 @@ int mp3_decode_init() {
is_table[1][6 - i] = v;
}
for(i=7;i<16;i++)
is_table[0][i] = is_table[1][i] = 0.0;
is_table[0][i] = is_table[1][i] = 0;
for(i=0;i<16;i++) {
double f;
@ -2540,7 +2540,7 @@ int mp3_decode_init() {
for(i=0;i<8;i++) {
float ci, cs, ca;
ci = ci_table[i];
cs = 1.0 / sqrt(1.0 + ci * ci);
cs = float(1.0 / sqrt(1.0 + ci * ci));
ca = cs * ci;
csa_table[i][0] = FIXHR(cs/4);
csa_table[i][1] = FIXHR(ca/4);

View File

@ -3,8 +3,8 @@
<head><title>OpenLara</title></head>
<body>
<span id="status">Starting...</span>
<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:<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>
<canvas id="canvas" width="16" height="16" oncontextmenu="event.preventDefault()"></canvas><br>
<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<br>FullScreen: Alt + Enter</span>
<script type='text/javascript'>
var statusElement = document.getElementById('status');

View File

@ -151,7 +151,9 @@
<ClCompile Include="main.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\animation.h" />
<ClInclude Include="..\..\camera.h" />
<ClInclude Include="..\..\character.h" />
<ClInclude Include="..\..\controller.h" />
<ClInclude Include="..\..\core.h" />
<ClInclude Include="..\..\debug.h" />
@ -166,6 +168,7 @@
<ClInclude Include="..\..\mesh.h" />
<ClInclude Include="..\..\shader.h" />
<ClInclude Include="..\..\sound.h" />
<ClInclude Include="..\..\sprite.h" />
<ClInclude Include="..\..\texture.h" />
<ClInclude Include="..\..\format.h" />
<ClInclude Include="..\..\trigger.h" />

View File

@ -311,14 +311,12 @@ int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi
if (time <= lastTime)
continue;
float slow = Input::down[ikR] ? 8.0f : 1.0f;
float delta = min(1.0f, (time - lastTime) * 0.001f);
EnterCriticalSection(&sndCS);
while (delta > EPS) {
Core::deltaTime = min(delta, 1.0f / 30.0f) / slow;
Core::deltaTime = min(delta, 1.0f / 30.0f);
Game::update();
delta -= Core::deltaTime * slow;
delta -= Core::deltaTime;
}
LeaveCriticalSection(&sndCS);
lastTime = time;

70
src/sprite.h Normal file
View File

@ -0,0 +1,70 @@
#ifndef H_SPRITE
#define H_SPRITE
#include "controller.h"
struct Sprite : Controller {
enum {
FRAME_ANIMATED = -1,
FRAME_RANDOM = -2,
};
bool instant;
int frame, flag;
float time;
Sprite(TR::Level *level, int entity, bool instant = true, int frame = FRAME_ANIMATED) : Controller(level, entity), instant(instant), flag(frame), time(0.0f) {
if (frame >= 0) { // specific frame
this->frame = frame;
} else if (frame == FRAME_RANDOM) { // random frame
this->frame = rand() % getSequence().sCount;
} else if (frame == FRAME_ANIMATED) { // animated
this->frame = 0;
}
}
static void add(TR::Level *level, TR::Entity::Type type, int room, int x, int y, int z, int frame = -1) {
int index = level->entityAdd(type, room, x, y, z, 0, -1);
if (index > -1) {
level->entities[index].intensity = 0x1FFF - level->rooms[room].ambient;
level->entities[index].controller = new Sprite(level, index, true, frame);
}
}
TR::SpriteSequence& getSequence() {
return level->spriteSequences[-(getEntity().modelIndex + 1)];
}
void update() {
if (flag >= 0) return;
bool remove = false;
time += Core::deltaTime;
if (flag == FRAME_ANIMATED) {
frame = int(time * SPRITE_FPS);
TR::SpriteSequence &seq = getSequence();
if (instant && frame >= seq.sCount)
remove = true;
else
frame %= seq.sCount;
} else
if (instant && time >= (1.0f / SPRITE_FPS))
remove = true;
if (remove) {
level->entityRemove(entity);
delete this;
}
}
virtual void render(Frustum *frustum, MeshBuilder *mesh) {
mat4 m(Core::mModel);
m.translate(pos);
Core::active.shader->setParam(uModel, m);
mesh->renderSprite(-(getEntity().modelIndex + 1), frame);
}
};
#endif

View File

@ -2,6 +2,7 @@
#define H_TRIGGER
#include "controller.h"
#include "sprite.h"
struct Trigger : Controller {
@ -50,7 +51,7 @@ struct Trigger : Controller {
}
if (!inState())
setState(state != baseState ? baseState : (entity.type == TR::Entity::TRAP_BLADE ? 2 : (baseState ^ 1)));
animation.setState(state != baseState ? baseState : (entity.type == TR::Entity::TRAP_BLADE ? 2 : (baseState ^ 1)));
updateAnimation(true);
updateEntity();
@ -58,7 +59,7 @@ struct Trigger : Controller {
};
struct Dart : Controller {
vec3 velocity;
vec3 dir;
bool inWall; // dart starts from wall
@ -67,7 +68,8 @@ struct Dart : Controller {
}
virtual void update() {
velocity = dir * level->anims[animIndex].speed;
TR::Animation *anim = animation;
velocity = dir * animation.getSpeed();
pos = pos + velocity * (Core::deltaTime * 30.0f);
updateEntity();
TR::Level::FloorInfo info;
@ -77,7 +79,7 @@ struct Dart : Controller {
TR::Entity &e = getEntity();
vec3 p = pos - dir * 64.0f; // wall offset = 64
addSprite(level, TR::Entity::SPARK, e.room, (int)p.x, (int)p.y, (int)p.z, SpriteController::FRAME_RANDOM);
Sprite::add(level, TR::Entity::SPARK, e.room, (int)p.x, (int)p.y, (int)p.z, Sprite::FRAME_RANDOM);
level->entityRemove(entity);
delete this;
@ -99,14 +101,13 @@ struct Dartgun : Trigger {
// add dart (bullet)
TR::Entity &entity = getEntity();
vec3 pos = vec3(0.0f, -512.0f, 256.0f).rotateY(PI - entity.rotation);
pos = pos + vec3(entity.x, entity.y, entity.z);
vec3 p = pos + vec3(0.0f, -512.0f, 256.0f).rotateY(PI - entity.rotation);
int dartIndex = level->entityAdd(TR::Entity::TRAP_DART, entity.room, (int)pos.x, (int)pos.y, (int)pos.z, entity.rotation, entity.intensity);
int dartIndex = level->entityAdd(TR::Entity::TRAP_DART, entity.room, (int)p.x, (int)p.y, (int)p.z, entity.rotation, entity.intensity);
if (dartIndex > -1)
level->entities[dartIndex].controller = new Dart(level, dartIndex);
addSprite(level, TR::Entity::SMOKE, entity.room, (int)pos.x, (int)pos.y, (int)pos.z);
Sprite::add(level, TR::Entity::SMOKE, entity.room, (int)p.x, (int)p.y, (int)p.z);
playSound(TR::SND_DART, pos, Sound::Flags::PAN);
@ -159,7 +160,7 @@ struct Block : Controller {
level->getFloorInfo(e.room, e.x + (int)dir.x, e.z + (int)dir.z, info, true);
if ((info.slantX | info.slantZ) || info.floor != e.y)
return false;
if (!setState(push ? STATE_PUSH : STATE_PULL))
if (!animation.setState(push ? STATE_PUSH : STATE_PULL))
return false;
updateFloor(false);
return true;

View File

@ -22,7 +22,7 @@
#ifdef PROFILE
#define LOG(...) printf(__VA_ARGS__)
#else
#define LOG(...)
#define LOG(...) 0
#endif
#endif
@ -115,6 +115,9 @@ struct vec3 {
float& operator [] (int index) const { return ((float*)this)[index]; }
bool operator == (float s) const { return x == s && y == s && z == s; }
bool operator != (float s) const { return !(*this == s); }
vec3& operator += (const vec3 &v) { x += v.x; y += v.y; z += v.z; return *this; }
vec3& operator -= (const vec3 &v) { x -= v.x; y -= v.y; z -= v.z; return *this; }
vec3& operator *= (const vec3 &v) { x *= v.x; y *= v.y; z *= v.z; return *this; }
@ -534,6 +537,14 @@ struct Box {
Box() {}
Box(const vec3 &min, const vec3 &max) : min(min), max(max) {}
vec3 center() const {
return (min + max) * 0.5f;
}
vec3 size() const {
return max - min;
}
void rotate90(int n) {
switch (n) {
case 0 : break;