1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-08-14 00:54:05 +02:00

#52 #3 fix objects interaction offset chaeck and alignment; fix blob shadows for animation with offset cmd; fix "multi-hit" for walls; fix climb on block above ceiling; fix aiming vertical offsets

This commit is contained in:
XProger
2017-05-13 15:55:31 +03:00
parent e0ac5e518a
commit 87b32cdf1a
6 changed files with 169 additions and 73 deletions

View File

@@ -250,9 +250,18 @@ struct Animation {
Box getBoundingBox(const vec3 &pos, int dir) { Box getBoundingBox(const vec3 &pos, int dir) {
if (!model) if (!model)
return Box(pos, pos); return Box(pos, pos);
vec3 min = frameA->box.min().lerp(frameB->box.min(), delta);
vec3 max = frameA->box.max().lerp(frameB->box.max(), delta); vec3 nextMin = frameB->box.min();
vec3 nextMax = frameB->box.max();
if (isPrepareToNext) {
nextMin += offset;
nextMax += offset;
}
vec3 min = frameA->box.min().lerp(nextMin, delta);
vec3 max = frameA->box.max().lerp(nextMax, delta);
Box box(min, max); Box box(min, max);
box.rotate90(dir); box.rotate90(dir);
box.min += pos; box.min += pos;

View File

@@ -543,19 +543,19 @@ struct Controller {
if (Core::pass != Core::passCompose || !TR::castShadow(entity.type)) if (Core::pass != Core::passCompose || !TR::castShadow(entity.type))
return; return;
Box box = animation.getBoundingBox(pos, 0);
vec3 center = box.center();
TR::Level::FloorInfo info; TR::Level::FloorInfo info;
level->getFloorInfo(entity.room, entity.x, entity.y, entity.z, info); level->getFloorInfo(entity.room, int(center.x), int(center.y), int(center.z), info);
Box box = animation.getBoundingBox(vec3(0, 0, 0), 0); const vec3 fpos = vec3(pos.x, info.floor - 16.0f, pos.z);
const vec3 size = box.size();
const vec3 fpos = vec3(float(entity.x), info.floor - 16.0f, float(entity.z));
const vec3 offset = box.center();
const vec3 size = box.size();
mat4 m = Core::mViewProj; mat4 m = Core::mViewProj;
m.translate(fpos); m.translate(fpos);
m.rotateY(angle.y); m.rotateY(angle.y);
m.translate(vec3(offset.x, 0.0f, offset.z)); m.translate(vec3(center.x - pos.x, 0.0f, center.z - pos.z));
m.scale(vec3(size.x, 0.0f, size.z) * (1.0f / 1024.0f)); m.scale(vec3(size.x, 0.0f, size.z) * (1.0f / 1024.0f));
Basis b; Basis b;
@@ -564,7 +564,7 @@ struct Controller {
game->setShader(Core::pass, Shader::FLASH, false, false); game->setShader(Core::pass, Shader::FLASH, false, false);
Core::active.shader->setParam(uViewProj, m); Core::active.shader->setParam(uViewProj, m);
Core::active.shader->setParam(uBasis, b); Core::active.shader->setParam(uBasis, b);
float alpha = lerp(0.7f, 0.90f, clamp((fpos.y - pos.y) / 1024.0f, 0.0f, 1.0f) ); float alpha = lerp(0.7f, 0.90f, clamp((fpos.y - box.max.y) / 1024.0f, 0.0f, 1.0f) );
Core::active.shader->setParam(uMaterial, vec4(vec3(0.5f * (1.0f - alpha)), alpha)); Core::active.shader->setParam(uMaterial, vec4(vec3(0.5f * (1.0f - alpha)), alpha));
Core::active.shader->setParam(uAmbient, vec3(0.0f)); Core::active.shader->setParam(uAmbient, vec3(0.0f));

View File

@@ -139,6 +139,42 @@ namespace TR {
CUTSCENE , // play cutscene CUTSCENE , // play cutscene
}; };
namespace Limits {
struct Limit {
float dy, dz, ay;
::Box box;
};
Limit SWITCH = {
0, 376, 30, {{-200, 0, 312}, {200, 0, 512}}
};
Limit SWITCH_UNDERWATER = {
0, 100, 80, {{-1024, -1024, -1024}, {1024, 1024, 512}}
};
Limit PICKUP = {
0, -100, 180, {{-256, -100, -256}, {256, 100, 100}}
};
Limit PICKUP_UNDERWATER = {
-200, -350, 45, {{-512, -512, -512}, {512, 512, 512}}
};
Limit KEY_HOLE = {
0, 362, 30, {{-200, 0, 312}, {200, 0, 512}}
};
Limit PUZZLE_HOLE = {
0, 327, 30, {{-200, 0, 312}, {200, 0, 512}}
};
Limit BLOCK = {
0, -612, 30, {{-300, 0, -692}, {300, 0, -512}}
};
}
#pragma pack(push, 1) #pragma pack(push, 1)
struct fixed { struct fixed {

View File

@@ -36,7 +36,7 @@
#define LARA_WET_TIMER (LARA_WET_SPECULAR / 16.0f) // 4 sec #define LARA_WET_TIMER (LARA_WET_SPECULAR / 16.0f) // 4 sec
#define PICKUP_FRAME_GROUND 40 #define PICKUP_FRAME_GROUND 40
#define PICKUP_FRAME_UNDERWATER 16 #define PICKUP_FRAME_UNDERWATER 18
#define PUZZLE_FRAME 80 #define PUZZLE_FRAME 80
#define MAX_TRIGGER_ACTIONS 64 #define MAX_TRIGGER_ACTIONS 64
@@ -466,8 +466,10 @@ struct Lara : Character {
if (room == TR::NO_ROOM) if (room == TR::NO_ROOM)
return; return;
if (level->rooms[room].flags.water) if (level->rooms[room].flags.water) {
stand = STAND_UNDERWATER; stand = STAND_UNDERWATER;
animation.setAnim(ANIM_UNDERWATER);
}
velocity = vec3(0.0f); velocity = vec3(0.0f);
@@ -1059,12 +1061,15 @@ struct Lara : Character {
Character *enemy = (Character*)e.controller; Character *enemy = (Character*)e.controller;
if (enemy->health <= 0) continue; if (enemy->health <= 0) continue;
vec3 p = enemy->getBoundingBox().center(); Box box = enemy->getBoundingBox();
vec3 p = box.center();
p.y = box.min.y + (box.max.y - box.min.y) / 3.0f;
vec3 v = p - pos; vec3 v = p - pos;
if (dir.dot(v.normal()) <= 0.5f) continue; // target is out of sight -60..+60 degrees if (dir.dot(v.normal()) <= 0.5f) continue; // target is out of sight -60..+60 degrees
float d = v.length(); float d = v.length();
if (d < dist && checkOcclusion(pos - vec3(0, 512, 0), p, d) ) { if (d < dist && checkOcclusion(pos - vec3(0, 650, 0), p, d) ) {
index = i; index = i;
dist = d; dist = d;
} }
@@ -1162,35 +1167,40 @@ struct Lara : Character {
} }
bool doPickUp() { bool doPickUp() {
if ((state != STATE_STOP && state != STATE_TREAD) || !animation.canSetState(STATE_PICK_UP))
return false;
int room = getRoomIndex(); int room = getRoomIndex();
TR::Entity &e = getEntity(); TR::Entity &e = getEntity();
TR::Limits::Limit limit = state == STATE_STOP ? TR::Limits::PICKUP : TR::Limits::PICKUP_UNDERWATER;
for (int i = 0; i < level->entitiesCount; i++) { for (int i = 0; i < level->entitiesCount; i++) {
TR::Entity &item = level->entities[i]; TR::Entity &item = level->entities[i];
if (item.room == room && !item.flags.invisible) { if (item.room == room && !item.flags.invisible) {
if (abs(item.x - e.x) > 256 || abs(item.z - e.z) > 256) if (!item.isItem())
continue;
Controller *controller = (Controller*)item.controller;
if (stand == STAND_UNDERWATER)
controller->angle.x = -25 * DEG2RAD;
controller->angle.y = angle.y;
if (!checkInteraction(controller, limit, (input & ACTION) != 0))
continue; continue;
if (item.isItem()) { alignByItem(controller, limit, true, false);
lastPickUp = i;
angle.x = 0.0f; if (stand == STAND_UNDERWATER)
pos = ((Controller*)item.controller)->pos; angle.x = -25 * DEG2RAD;
if (stand == STAND_UNDERWATER) { // TODO: lerp to pos/angle
pos -= getDir() * 256.0f; lastPickUp = i;
pos.y -= 256; return true;
}
updateEntity();
return true;
}
} }
} }
return false; return false;
} }
bool checkAngle(TR::angle rotation) {
return fabsf(shortAngle(rotation, getEntity().rotation)) < PI * 0.25f;
}
bool useItem(TR::Entity::Type item, TR::Entity::Type slot) { bool useItem(TR::Entity::Type item, TR::Entity::Type slot) {
if (item == TR::Entity::NONE) { if (item == TR::Entity::NONE) {
switch (slot) { switch (slot) {
@@ -1207,6 +1217,33 @@ struct Lara : Character {
return false; return false;
} }
void alignByItem(Controller *item, const TR::Limits::Limit &limit, bool dx, bool ay) {
if (ay)
angle = item->angle;
else
angle.x = angle.z = 0.0f;
mat4 m = item->getMatrix();
float fx = 0.0f;
if (!dx)
fx = (m.transpose() * vec4(pos - item->pos, 0.0f)).x;
pos = item->pos + (m * vec4(fx, limit.dy, limit.dz, 0.0f)).xyz;
velocity = vec3(0.0f);
speed = 0.0f;
updateEntity();
}
bool checkInteraction(Controller *controller, const TR::Limits::Limit &limit, bool action) {
if ((state != STATE_STOP && state != STATE_TREAD && state != STATE_PUSH_PULL_READY) || !action || !emptyHands())
return false;
vec3 delta = (controller->getMatrix().transpose() * vec4(pos - controller->pos, 0.0f)).xyz; // inverse transform
return limit.box.contains(delta) && fabsf(shortAngle(angle.y, controller->angle.y)) <= limit.ay * DEG2RAD;
}
void checkTrigger() { void checkTrigger() {
if (actionCommand) return; if (actionCommand) return;
@@ -1226,6 +1263,8 @@ struct Lara : Character {
if (info.trigInfo.once == 1 && isActive) return; // once trigger is already activated if (info.trigInfo.once == 1 && isActive) return; // once trigger is already activated
TR::Limits::Limit *limit = NULL;
int actionState = state; int actionState = state;
switch (info.trigger) { switch (info.trigger) {
case TR::Level::Trigger::ACTIVATE : case TR::Level::Trigger::ACTIVATE :
@@ -1236,26 +1275,28 @@ struct Lara : Character {
break; break;
case TR::Level::Trigger::SWITCH : case TR::Level::Trigger::SWITCH :
actionState = (isActive && stand == STAND_GROUND) ? STATE_SWITCH_UP : STATE_SWITCH_DOWN; actionState = (isActive && stand == STAND_GROUND) ? STATE_SWITCH_UP : STATE_SWITCH_DOWN;
if (!isPressed(ACTION) || state == actionState || !emptyHands()) if (!animation.canSetState(actionState))
return;
if (!checkAngle(level->entities[info.trigCmd[0].args].rotation))
return; return;
limit = state == STATE_STOP ? &TR::Limits::SWITCH : &TR::Limits::SWITCH_UNDERWATER;
{
Trigger *controller = (Trigger*)level->entities[info.trigCmd[0].args].controller;
if (!controller->inState() || !checkInteraction(controller, *limit, isPressed(ACTION)))
return;
}
break; break;
case TR::Level::Trigger::KEY : case TR::Level::Trigger::KEY :
if (level->entities[info.trigCmd[0].args].flags.active) if (level->entities[info.trigCmd[0].args].flags.active)
return; return;
actionState = level->entities[info.trigCmd[0].args].type == TR::Entity::HOLE_KEY ? STATE_USE_KEY : STATE_USE_PUZZLE; actionState = level->entities[info.trigCmd[0].args].type == TR::Entity::HOLE_KEY ? STATE_USE_KEY : STATE_USE_PUZZLE;
if (isActive || !isPressed(ACTION) || state == actionState || !emptyHands()) // TODO: STATE_USE_PUZZLE if (!animation.canSetState(actionState))
return; return;
if (!checkAngle(level->entities[info.trigCmd[0].args].rotation)) limit = actionState == STATE_USE_KEY ? &TR::Limits::KEY_HOLE : &TR::Limits::PUZZLE_HOLE;
return; if (!checkInteraction((Controller*)level->entities[info.trigCmd[0].args].controller, *limit, isPressed(ACTION)))
if (animation.canSetState(actionState)) { return;
if (!useItem(TR::Entity::NONE, level->entities[info.trigCmd[0].args].type)) { if (!useItem(TR::Entity::NONE, level->entities[info.trigCmd[0].args].type)) {
playSound(TR::SND_NO, pos, Sound::PAN); playSound(TR::SND_NO, pos, Sound::PAN);
return;
}
} else
return; return;
}
lastPickUp = info.trigCmd[0].args; // TODO: it's not pickup, it's key/puzzle hole lastPickUp = info.trigCmd[0].args; // TODO: it's not pickup, it's key/puzzle hole
break; break;
case TR::Level::Trigger::PICKUP : case TR::Level::Trigger::PICKUP :
@@ -1270,17 +1311,11 @@ struct Lara : Character {
// try to activate Lara state // try to activate Lara state
if (!animation.setState(actionState)) return; if (!animation.setState(actionState)) return;
if (info.trigger == TR::Level::Trigger::SWITCH || info.trigger == TR::Level::Trigger::KEY) { if (info.trigger == TR::Level::Trigger::KEY)
if (info.trigger == TR::Level::Trigger::KEY) level->entities[info.trigCmd[0].args].flags.active = true;
level->entities[info.trigCmd[0].args].flags.active = true;
TR::Entity &p = level->entities[info.trigCmd[0].args]; if (limit)
angle.y = p.rotation; alignByItem((Controller*)level->entities[info.trigCmd[0].args].controller, *limit, stand != STAND_GROUND || info.trigger != TR::Level::Trigger::SWITCH, true);
angle.x = 0;
pos = ((Controller*)p.controller)->pos + vec3(sinf(angle.y), 0, cosf(angle.y)) * (stand == STAND_GROUND ? 384.0f : 128.0f);
velocity = vec3(0.0f);
updateEntity();
}
// build trigger activation chain // build trigger activation chain
ActionCommand *actionItem = &actionList[1]; ActionCommand *actionItem = &actionList[1];
@@ -1403,10 +1438,11 @@ struct Lara : Character {
if (c.side != Collision::FRONT) if (c.side != Collision::FRONT)
return state; return state;
int floor = c.info[Collision::FRONT].floor; int floor = c.info[Collision::FRONT].floor;
int ceiling = c.info[Collision::FRONT].ceiling;
int hands = int(bounds.min.y); int hands = int(bounds.min.y);
if (abs(floor - hands) < 128) { if (abs(floor - hands) < 64 && floor != ceiling) {
alignToWall(-LARA_RADIUS); alignToWall(-LARA_RADIUS);
pos.y = float(floor + LARA_HANG_OFFSET); pos.y = float(floor + LARA_HANG_OFFSET);
stand = STAND_HANG; stand = STAND_HANG;
@@ -1458,17 +1494,21 @@ struct Lara : Character {
for (int i = 0; i < level->entitiesCount; i++) { for (int i = 0; i < level->entitiesCount; i++) {
TR::Entity &e = level->entities[i]; TR::Entity &e = level->entities[i];
if (!e.isBlock())
continue;
int q = entityQuadrant(e); Block *block = (Block*)e.controller;
int dx = abs(int(pos.x) - e.x); float oldAngle = block->angle.y;
int dz = abs(int(pos.z) - e.z); block->angle.y = angleQuadrant(angle.y) * (PI * 0.5f);
if (q > -1 && e.isBlock() && dx < 1024 && dz < 1024 && e.y == y && alignToWall(-LARA_RADIUS, q, 64 + int(LARA_RADIUS), 512 - int(LARA_RADIUS))) { if (!checkInteraction(block, TR::Limits::BLOCK, (input & ACTION) != 0)) {
Block *block = (Block*)e.controller; block->angle.y = oldAngle;
block->angle.y = angle.y; continue;
block->updateEntity();
return block;
} }
alignByItem(block, TR::Limits::BLOCK, false, true);
return block;
} }
return NULL; return NULL;
} }
@@ -1476,15 +1516,19 @@ struct Lara : Character {
virtual int getStateGround() { virtual int getStateGround() {
angle.x = 0.0f; angle.x = 0.0f;
if ((input & ACTION) && emptyHands() && doPickUp()) if ((state == STATE_STOP || state == STATE_TREAD) && (input & ACTION) && emptyHands() && doPickUp())
return STATE_PICK_UP; return STATE_PICK_UP;
if ((input & (FORTH | ACTION)) == (FORTH | ACTION) && (animation.index == ANIM_STAND || animation.index == ANIM_STAND_NORMAL) && emptyHands() && collision.side == Collision::FRONT) { // TODO: get rid of animation.index if ((input & (FORTH | ACTION)) == (FORTH | ACTION) && (animation.index == ANIM_STAND || animation.index == ANIM_STAND_NORMAL) && emptyHands() && collision.side == Collision::FRONT) { // TODO: get rid of animation.index
int floor = collision.info[Collision::FRONT].floor; int floor = collision.info[Collision::FRONT].floor;
int ceiling = collision.info[Collision::FRONT].ceiling;
int h = (int)pos.y - floor; int h = (int)pos.y - floor;
int aIndex = animation.index; int aIndex = animation.index;
if (h <= 2 * 256 + 128) { if (floor == ceiling)
;// do nothing
else if (h <= 2 * 256 + 128) {
aIndex = ANIM_CLIMB_2; aIndex = ANIM_CLIMB_2;
pos.y = floor + 512.0f; pos.y = floor + 512.0f;
} else if (h <= 3 * 256 + 128) { } else if (h <= 3 * 256 + 128) {
@@ -1539,10 +1583,8 @@ struct Lara : Character {
if (state == STATE_PUSH_PULL_READY && (input & (FORTH | BACK))) { if (state == STATE_PUSH_PULL_READY && (input & (FORTH | BACK))) {
int pushState = (input & FORTH) ? STATE_PUSH_BLOCK : STATE_PULL_BLOCK; int pushState = (input & FORTH) ? STATE_PUSH_BLOCK : STATE_PULL_BLOCK;
Block *block = getBlock(); Block *block = getBlock();
if (block && animation.canSetState(pushState) && block->doMove((input & FORTH) != 0)) { if (block && animation.canSetState(pushState) && block->doMove((input & FORTH) != 0))
alignToWall(-LARA_RADIUS);
return pushState; return pushState;
}
} }
if (state == STATE_PUSH_PULL_READY || getBlock()) if (state == STATE_PUSH_PULL_READY || getBlock())
@@ -2113,7 +2155,9 @@ struct Lara : Character {
break; break;
case STAND_GROUND : case STAND_GROUND :
case STAND_HANG : case STAND_HANG :
if (opos.y - floor > (256 * 3 - 128) && state == STATE_RUN) if (stand == STAND_GROUND && (pos - opos).length2() < 16)
animation.setAnim(ANIM_STAND_NORMAL);
else if (opos.y - floor > (256 * 3 - 128) && state == STATE_RUN)
animation.setAnim(isLeftFoot ? ANIM_SMASH_RUN_LEFT : ANIM_SMASH_RUN_RIGHT); animation.setAnim(isLeftFoot ? ANIM_SMASH_RUN_LEFT : ANIM_SMASH_RUN_RIGHT);
else if (stand == STAND_HANG) else if (stand == STAND_HANG)
animation.setAnim(ANIM_HANG, -21); animation.setAnim(ANIM_HANG, -21);

View File

@@ -492,8 +492,9 @@ struct Level : IGame {
TR::Room &room = level.rooms[entity.room]; TR::Room &room = level.rooms[entity.room];
if (!room.flags.rendered || entity.flags.invisible || entity.flags.rendered) if (entity.type != TR::Entity::LARA) // TODO: remove this hack (collect conjugate room entities)
return; if (!room.flags.rendered || entity.flags.invisible || entity.flags.rendered)
return;
int16 lum = entity.intensity == -1 ? room.ambient : entity.intensity; int16 lum = entity.intensity == -1 ? room.ambient : entity.intensity;

View File

@@ -140,6 +140,8 @@ struct vec2 {
inline bool operator != (const vec2 &v) const { return !(*this == v); } inline bool operator != (const vec2 &v) const { return !(*this == v); }
inline bool operator == (float s) const { return x == s && y == s; } inline bool operator == (float s) const { return x == s && y == s; }
inline bool operator != (float s) const { return !(*this == s); } inline bool operator != (float s) const { return !(*this == s); }
inline bool operator < (const vec2 &v) const { return x < v.x && y < v.y; }
inline bool operator > (const vec2 &v) const { return x > v.x && y > v.y; }
inline vec2 operator - () const { return vec2(-x, -y); } inline vec2 operator - () const { return vec2(-x, -y); }
vec2& operator += (const vec2 &v) { x += v.x; y += v.y; return *this; } vec2& operator += (const vec2 &v) { x += v.x; y += v.y; return *this; }
@@ -165,6 +167,7 @@ struct vec2 {
float length2() const { return dot(*this); } float length2() const { return dot(*this); }
float length() const { return sqrtf(length2()); } float length() const { return sqrtf(length2()); }
vec2 abs() const { return vec2(fabsf(x), fabsf(y)); }
vec2 normal() const { float s = length(); return s == 0.0 ? (*this) : (*this)*(1.0f/s); } vec2 normal() const { float s = length(); return s == 0.0 ? (*this) : (*this)*(1.0f/s); }
float angle() const { return atan2f(y, x); } float angle() const { return atan2f(y, x); }
vec2& rotate(const vec2 &cs) { *this = vec2(x*cs.x - y*cs.y, x*cs.y + y*cs.x); return *this; } vec2& rotate(const vec2 &cs) { *this = vec2(x*cs.x - y*cs.y, x*cs.y + y*cs.x); return *this; }
@@ -189,6 +192,8 @@ struct vec3 {
inline bool operator != (const vec3 &v) const { return !(*this == v); } inline bool operator != (const vec3 &v) const { return !(*this == v); }
inline bool operator == (float s) const { return x == s && y == s && z == s; } inline bool operator == (float s) const { return x == s && y == s && z == s; }
inline bool operator != (float s) const { return !(*this == s); } inline bool operator != (float s) const { return !(*this == s); }
inline bool operator < (const vec3 &v) const { return x < v.x && y < v.y && z < v.z; }
inline bool operator > (const vec3 &v) const { return x > v.x && y > v.y && z > v.z; }
inline vec3 operator - () const { return vec3(-x, -y, -z); } inline vec3 operator - () const { return vec3(-x, -y, -z); }
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; }
@@ -214,6 +219,7 @@ struct vec3 {
float length2() const { return dot(*this); } float length2() const { return dot(*this); }
float length() const { return sqrtf(length2()); } float length() const { return sqrtf(length2()); }
vec3 abs() const { return vec3(fabsf(x), fabsf(y), fabsf(z)); }
vec3 normal() const { float s = length(); return s == 0.0f ? (*this) : (*this)*(1.0f/s); } vec3 normal() const { float s = length(); return s == 0.0f ? (*this) : (*this)*(1.0f/s); }
vec3 axisXZ() const { return (fabsf(x) > fabsf(z)) ? vec3(float(sign(x)), 0, 0) : vec3(0, 0, float(sign(z))); } vec3 axisXZ() const { return (fabsf(x) > fabsf(z)) ? vec3(float(sign(x)), 0, 0) : vec3(0, 0, float(sign(z))); }
@@ -814,7 +820,7 @@ struct Box {
} }
bool contains(const vec3 &v) const { bool contains(const vec3 &v) const {
return v.x >= min.x && v.x <= max.x && v.y >= min.y && v.y <= max.x && v.z >= min.z && v.z <= max.z; return v.x >= min.x && v.x <= max.x && v.y >= min.y && v.y <= max.y && v.z >= min.z && v.z <= max.z;
} }
vec3 pushOut2D(const vec3 &v) const { vec3 pushOut2D(const vec3 &v) const {