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

#3 underwater acceleration and tilt; #4 new collision detection system for walls (slide and rotate along walls); #8 first person view mode (V key); auto-rotate camera to the back while moving

This commit is contained in:
XProger 2017-02-15 04:11:26 +03:00
parent ab89e08549
commit d9fce4457d
15 changed files with 532 additions and 202 deletions

Binary file not shown.

View File

@ -145,7 +145,7 @@ struct Animation {
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 true;
}
}
}

View File

@ -13,28 +13,21 @@ struct Camera : Controller {
Frustum *frustum;
float fov, znear, zfar;
vec3 target, destPos, lastDest, angleAdv;
vec3 target, destPos, lastDest, advAngle;
float advTimer;
mat4 mViewInv;
int room;
float timer;
int actTargetEntity, actCamera;
bool cutscene;
bool firstPerson;
Basis prevBasis;
vec4 *reflectPlane;
Camera(IGame *game, Lara *owner) : Controller(game, owner ? owner->entity : 0), owner(owner), frustum(new Frustum()), timer(0.0f), actTargetEntity(-1), actCamera(-1), reflectPlane(NULL) {
fov = 65.0f;
znear = 16;
zfar = 40.0f * 1024.0f;
angleAdv = vec3(0.0f);
if (owner) {
room = owner->getEntity().room;
pos = pos - owner->getDir() * 1024.0f;
target = owner->getViewPoint();
}
changeView(false);
cutscene = owner->getEntity().type != TR::Entity::LARA && level->cameraFrames;
}
@ -46,6 +39,30 @@ struct Camera : Controller {
return actCamera > -1 ? level->cameras[actCamera].room : room;
}
virtual void checkRoom() {
TR::Level::FloorInfo info;
level->getFloorInfo(room, (int)pos.x, (int)pos.y, (int)pos.z, info);
if (info.roomNext != TR::NO_ROOM)
room = info.roomNext;
if (pos.y < info.roomCeiling) {
if (info.roomAbove != TR::NO_ROOM)
room = info.roomAbove;
else
if (info.roomCeiling != 0xffff8100)
pos.y = (float)info.roomCeiling;
}
if (pos.y > info.roomFloor) {
if (info.roomBelow != TR::NO_ROOM)
room = info.roomBelow;
else
if (info.roomFloor != 0xffff8100)
pos.y = (float)info.roomFloor;
}
}
virtual bool activate(ActionCommand *cmd) {
Controller::activate(cmd);
this->timer = max(max(1.0f, this->timer), cmd->timer);
@ -88,18 +105,43 @@ struct Camera : Controller {
} else
#endif
{
vec3 advAngleOld = advAngle;
if (Input::down[ikMouseL]) {
vec2 delta = Input::mouse.pos - Input::mouse.start.L;
angleAdv.x -= delta.y * 0.01f;
angleAdv.y += delta.x * 0.01f;
advAngle.x -= delta.y * 0.01f;
advAngle.y += delta.x * 0.01f;
Input::mouse.start.L = Input::mouse.pos;
}
angleAdv.x -= Input::joy.R.y * 2.0f * Core::deltaTime;
angleAdv.y += Input::joy.R.x * 2.0f * Core::deltaTime;
angle = owner->angle + angleAdv;
angle.z = 0.0f;
advAngle.x -= Input::joy.R.y * 2.0f * Core::deltaTime;
advAngle.y += Input::joy.R.x * 2.0f * Core::deltaTime;
if (advAngleOld == advAngle) {
if (advTimer > 0.0f) {
advTimer -= Core::deltaTime;
if (advTimer <= 0.0f)
advTimer = 0.0f;
}
} else
advTimer = -1.0f;
if (owner->velocity != 0.0f && advTimer < 0.0f && !Input::down[ikMouseL])
advTimer = -advTimer;
if (advTimer == 0.0f && advAngle != 0.0f) {
float t = 10.0f * Core::deltaTime;
advAngle.x = lerp(clampAngle(advAngle.x), 0.0f, t);
advAngle.y = lerp(clampAngle(advAngle.y), 0.0f, t);
}
angle = owner->angle + advAngle;
angle.z = 0.0f;
if (owner->stand == Lara::STAND_ONWATER)
angle.x -= 22.0f * DEG2RAD;
if (owner->state == Lara::STATE_HANG || owner->state == Lara::STATE_HANG_LEFT || owner->state == Lara::STATE_HANG_RIGHT)
angle.x -= 60.0f * DEG2RAD;
#ifdef LEVEL_EDITOR
angle = angleAdv;
@ -124,7 +166,6 @@ struct Camera : Controller {
return;
#endif
int lookAt = -1;
if (actTargetEntity > -1) lookAt = actTargetEntity;
if (owner->target > -1) lookAt = owner->target;
@ -142,6 +183,25 @@ struct Camera : Controller {
}
}
if (firstPerson && actCamera == -1) {
Basis head = owner->animation.getJoints(owner->getMatrix(), 14, true);
Basis eye(quat(0.0f, 0.0f, 0.0f, 1.0f), vec3(0.0f, -40.0f, 10.0f));
eye = head * eye;
mViewInv.identity();
//prevBasis = prevBasis.lerp(eye, 15.0f * Core::deltaTime);
mViewInv.setRot(eye.rot);
mViewInv.setPos(eye.pos);
mViewInv.rotateY(advAngle.y);
mViewInv.rotateX(advAngle.x + PI);
pos = mViewInv.getPos();
checkRoom();
Sound::listener.matrix = mViewInv;
return;
}
float lerpFactor = (lookAt == -1) ? 6.0f : 10.0f;
vec3 dir;
target = target.lerp(owner->getViewPoint(), lerpFactor * Core::deltaTime);
@ -170,34 +230,13 @@ struct Camera : Controller {
} 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, destRoom, true);
}
}
room = destRoom;
}
pos = pos.lerp(destPos, Core::deltaTime * lerpFactor);
if (actCamera <= -1) {
TR::Level::FloorInfo info;
level->getFloorInfo(room, (int)pos.x, (int)pos.y, (int)pos.z, info);
if (info.roomNext != 255)
room = info.roomNext;
if (pos.y < info.roomCeiling) {
if (info.roomAbove != 255)
room = info.roomAbove;
else
if (info.roomCeiling != 0xffff8100)
pos.y = (float)info.roomCeiling;
}
if (pos.y > info.roomFloor) {
if (info.roomBelow != 255)
room = info.roomBelow;
else
if (info.roomFloor != 0xffff8100)
pos.y = (float)info.roomFloor;
}
}
if (actCamera <= -1)
checkRoom();
}
mViewInv = mat4(pos, target, vec3(0, -1, 0));
@ -226,6 +265,20 @@ struct Camera : Controller {
frustum->pos = Core::viewPos;
frustum->calcPlanes(Core::mViewProj);
}
void changeView(bool firstPerson) {
this->firstPerson = firstPerson;
room = owner->getRoomIndex();
pos = owner->pos - owner->getDir() * 1024.0f;
target = owner->getViewPoint();
advAngle = vec3(0.0f);
advTimer = 0.0f;
fov = firstPerson ? 90.0f : 65.0f;
znear = firstPerson ? 8.0f : 16.0f;
zfar = 40.0f * 1024.0f;
}
};
#endif

View File

@ -29,12 +29,24 @@ struct Character : Controller {
vec3 velocity;
float angleExt;
float speed;
Collision collision;
Character(IGame *game, int entity, int health) : Controller(game, entity), target(-1), health(health), tilt(0.0f), stand(STAND_GROUND), lastInput(0), velocity(0.0f) {
animation.initOverrides();
rotHead = rotChest = quat(0, 0, 0, 1);
}
void rotateY(float delta) {
angle.y += delta;
velocity = velocity.rotateY(-delta);
}
void rotateX(float delta) {
angle.x = clamp(angle.x + delta, -PI * 0.49f, PI * 0.49f);
}
virtual void hit(int damage, Controller *enemy = NULL) {
health -= damage;
};
@ -44,13 +56,13 @@ struct Character : Controller {
TR::Entity &e = getEntity();
level->getFloorInfo(e.room, e.x, e.y, e.z, info);
if (info.roomNext != 0xFF)
if (info.roomNext != TR::NO_ROOM)
e.room = info.roomNext;
if (info.roomBelow != 0xFF && e.y > info.roomFloor)
if (info.roomBelow != TR::NO_ROOM && e.y > info.roomFloor)
e.room = info.roomBelow;
if (info.roomAbove != 0xFF && e.y <= info.roomCeiling) {
if (info.roomAbove != TR::NO_ROOM && e.y <= info.roomCeiling) {
if (stand == STAND_UNDERWATER && !level->rooms[info.roomAbove].flags.water) {
stand = STAND_ONWATER;
velocity.y = 0;
@ -106,18 +118,13 @@ struct Character : Controller {
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);
if (input & LEFT) tilt -= tiltSpeed;
if (input & RIGHT) tilt += tiltSpeed;
tilt = clamp(tilt, -tiltMax, +tiltMax);
} else {
if (tilt > 0.0f) tilt = max(0.0f, tilt - tiltSpeed);
if (tilt < 0.0f) tilt = min(0.0f, tilt + tiltSpeed);
}
angle.z = tilt;
}

104
src/collision.h Normal file
View File

@ -0,0 +1,104 @@
#ifndef H_COLLISION
#define H_COLLISION
#include "core.h"
#include "utils.h"
#include "format.h"
struct Collision {
enum Side { NONE, LEFT, RIGHT, FRONT, TOP, BOTTOM } side;
struct Info {
int room, roomAbove, roomBelow, floor, ceiling;
} info[4];
Collision() : side(NONE) {}
Collision(TR::Level *level, int room, vec3 &pos, const vec3 &offset, const vec3 &velocity, float radius, float angle, int minHeight, int maxHeight, int maxAscent, int maxDescent) {
if (velocity.x > 0.0f || velocity.z > 0.0f)
angle = normalizeAngle(PI2 + vec2(velocity.z, velocity.x).angle());
pos += velocity;
int q = angleQuadrant(angle);
const vec2 v[] = {
{ -radius, radius },
{ radius, radius },
{ radius, -radius },
{ -radius, -radius },
};
const vec2 &l = v[q], &r = v[(q + 1) % 4];
vec2 f = (q %= 2) ? vec2(l.x, radius * cosf(angle)) : vec2(radius * sinf(angle), l.y),
p(pos.x, pos.z),
d(0.0F);
vec3 hpos = pos + offset;
int height = maxHeight - minHeight;
if (checkHeight(level, room, hpos, vec2(0.0f), height, 0xFFFFFF, 0xFFFFFF, side = NONE)) {
pos -= velocity;
side = FRONT;
return;
}
if (info[NONE].ceiling > hpos.y - maxHeight) {
pos.y = info[NONE].ceiling + maxHeight - offset.y;
side = TOP;
}
if (info[NONE].floor < hpos.y + minHeight) {
pos.y = info[NONE].floor - minHeight - offset.y;
side = BOTTOM;
}
if (checkHeight(level, room, hpos, f, height, maxAscent, maxDescent, FRONT)) {
d = vec2(-velocity.x, -velocity.z);
q ^= 1;
d[q] = getOffset(p[q] + f[q], p[q]);
} else if (checkHeight(level, room, hpos, l, height, maxAscent, maxDescent, LEFT)) {
d[q] = getOffset(p[q] + l[q], p[q] + f[q]);
} else if (checkHeight(level, room, hpos, r, height, maxAscent, maxDescent, RIGHT)) {
d[q] = getOffset(p[q] + r[q], p[q] + f[q]);
} else
return;
pos += vec3(d.x, 0.0f, d.y);
}
inline bool checkHeight(TR::Level *level, int room, const vec3 &pos, const vec2 &offset, int height, int maxAscent, int maxDescent, Side side) {
TR::Level::FloorInfo info;
int py = int(pos.y);
level->getFloorInfo(room, int(pos.x + offset.x), py, int(pos.z + offset.y), info);
Info &inf = this->info[side];
inf.room = info.roomNext != TR::NO_ROOM ? info.roomNext : room;
inf.roomAbove = info.roomAbove;
inf.roomBelow = info.roomBelow;
inf.floor = info.floor;
inf.ceiling = info.ceiling;
if ((info.ceiling == info.floor) || (info.floor - info.ceiling < height) || (py - info.floor > maxAscent) || (info.floor - py > maxDescent) || (info.ceiling > py)) {
this->side = side;
return true;
}
return false;
}
inline float getOffset(float from, float to) {
int a = int(from) / 1024;
int b = int(to) / 1024;
from -= float(a * 1024.0f);
if (b == a)
return 0.0f;
else if (b > a)
return -from + 1025.0f;
return -from - 1.0f;
}
};
#endif

View File

@ -5,6 +5,7 @@
#include "frustum.h"
#include "mesh.h"
#include "animation.h"
#include "collision.h"
#define GRAVITY (6.0f * 30.0f)
#define NO_OVERLAP 0x7FFFFFFF
@ -119,13 +120,14 @@ struct Controller {
return false;
}
void updateEntity() {
TR::Entity &e = getEntity();
e.x = int(pos.x);
e.y = int(pos.y);
e.z = int(pos.z);
while (angle.y < 0.0f) angle.y += 2 * PI;
while (angle.y > 2 * PI) angle.y -= 2 * PI;
angle.y = normalizeAngle(angle.y);
e.rotation = angle.y;
}
@ -220,26 +222,18 @@ struct Controller {
}
void alignToWall(float offset = 0.0f) {
float fx = pos.x / 1024.0f;
float fz = pos.z / 1024.0f;
fx -= (int)fx;
fz -= (int)fz;
int q = angleQuadrant(angle.y);
int x = int(pos.x) & ~1023;
int z = int(pos.z) & ~1023;
int k;
if (fx > 1.0f - fz)
k = fx < fz ? 0 : 1;
else
k = fx < fz ? 3 : 2;
angle.y = k * PI * 0.5f; // clamp angle to n*PI/2
if (offset != 0.0f) {
vec3 dir = getDir() * (512.0f - offset);
if (k % 2)
pos.x = int(pos.x / 1024.0f) * 1024.0f + 512.0f + dir.x;
else
pos.z = int(pos.z / 1024.0f) * 1024.0f + 512.0f + dir.z;
switch (q) {
case 0 : pos.z = z + 1024 + offset; break;
case 1 : pos.x = x + 1024 + offset; break;
case 2 : pos.z = z - offset; break;
case 3 : pos.x = x - offset; break;
}
angle.y = q * (PI * 0.5f);
updateEntity();
}
@ -264,7 +258,7 @@ struct Controller {
if (lr != room || lx != sx || lz != sz) {
level->getFloorInfo(room, sx, py, sz, info);
if (info.roomNext != 0xFF) {
if (info.roomNext != TR::NO_ROOM) {
room = info.roomNext;
level->getFloorInfo(room, sx, py, sz, info);
}
@ -274,9 +268,9 @@ struct Controller {
}
if (isCamera) {
if (py > info.floor && info.roomBelow != 0xFF)
if (py > info.floor && info.roomBelow != TR::NO_ROOM)
room = info.roomBelow;
else if (py < info.ceiling && info.roomAbove != 0xFF)
else if (py < info.ceiling && info.roomAbove != TR::NO_ROOM)
room = info.roomAbove;
else if (py > info.floor || py < info.ceiling) {
int minX = px / 1024 * 1024;
@ -289,14 +283,14 @@ struct Controller {
}
} else {
if (py > info.floor) {
if (info.roomBelow != 0xFF)
if (info.roomBelow != TR::NO_ROOM)
room = info.roomBelow;
else
break;
}
if (py < info.ceiling) {
if (info.roomAbove != 0xFF)
if (info.roomAbove != TR::NO_ROOM)
room = info.roomAbove;
else
break;

View File

@ -179,7 +179,7 @@ struct Wolf : Enemy {
return (state == STATE_STOP || state == STATE_SLEEP) ? STATE_SLEEP : STATE_STOP;
switch (state) {
case STATE_SLEEP : return target > -1 ? STATE_STOP : state;
case STATE_SLEEP : return (target > -1 && level->entities[target].room == getRoomIndex()) ? STATE_STOP : state;
case STATE_STOP : return target > -1 ? STATE_HOWL : STATE_SLEEP;
case STATE_HOWL : return state;
case STATE_GROWL : return target > -1 ? (randf() > 0.5f ? STATE_STALKING : STATE_RUN) : STATE_STOP;

View File

@ -1551,8 +1551,8 @@ namespace TR {
int sx = x - room.info.x;
int sz = z - room.info.z;
sx = clamp(sx, 0, (room.xSectors - 1) * 1024);
sz = clamp(sz, 0, (room.zSectors - 1) * 1024);
sx = clamp(sx, 0, room.xSectors * 1024 - 1);
sz = clamp(sz, 0, room.zSectors * 1024 - 1);
dx = sx % 1024;
dz = sz % 1024;
@ -1584,13 +1584,13 @@ namespace TR {
return;
Room::Sector *sBelow = &s;
while (sBelow->roomBelow != 0xFF) sBelow = &getSector(sBelow->roomBelow, x, z, dx, dz);
while (sBelow->roomBelow != TR::NO_ROOM) sBelow = &getSector(sBelow->roomBelow, x, z, dx, dz);
info.floor = 256 * sBelow->floor;
parseFloorData(info, sBelow->floorIndex, dx, dz);
if (info.roomNext == 0xFF) {
if (info.roomNext == TR::NO_ROOM) {
Room::Sector *sAbove = &s;
while (sAbove->roomAbove != 0xFF) sAbove = &getSector(sAbove->roomAbove, x, z, dx, dz);
while (sAbove->roomAbove != TR::NO_ROOM) sAbove = &getSector(sAbove->roomAbove, x, z, dx, dz);
if (sAbove != sBelow) {
info.ceiling = 256 * sAbove->ceiling;
parseFloorData(info, sAbove->floorIndex, dx, dz);

View File

@ -39,13 +39,21 @@ namespace Game {
void update() {
float dt = Core::deltaTime;
if (Input::down[ikV]) { // third <-> first person view
level->camera->changeView(!level->camera->firstPerson);
Input::down[ikV] = false;
}
if (Input::down[ikR]) // slow motion (for animation debugging)
Core::deltaTime /= 10.0f;
if (Input::down[ikT])
Core::deltaTime *= 10.0f;
if (Input::down[ikT]) // fast motion
for (int i = 0; i < 9; i++)
level->update();
level->update();
Core::deltaTime = dt;
}

View File

@ -12,16 +12,25 @@
#define TURN_FAST_BACK PI * 3.0f / 4.0f
#define TURN_NORMAL PI / 2.0f
#define TURN_SLOW PI / 3.0f
#define TURN_WATER_FAST PI * 3.0f / 4.0f
#define TURN_WATER_SLOW PI * 2.0f / 3.0f
#define TURN_WATER_FAST (DEG2RAD * 150.0f)
#define TURN_WATER_SLOW (DEG2RAD * 60.0f)
#define TURN_WALL_Y (DEG2RAD * 150.0f)
#define TURN_WALL_X (DEG2RAD * 60.0f)
#define TURN_WALL_X_CLAMP (DEG2RAD * 35.0f)
#define TILT_MAX (PI / 18.0f)
#define TILT_SPEED TILT_MAX
#define LARA_TILT_SPEED (DEG2RAD * 37.5f)
#define LARA_TILT_MAX (DEG2RAD * 10.0f)
#define GLIDE_SPEED 35.0f
#define SWIM_SPEED 45.0f
#define LARA_HANG_OFFSET 724
#define LARA_HEIGHT 762
#define LARA_HEIGHT_WATER 400
#define LARA_RADIUS 100.0f
#define LARA_RADIUS_WATER 300.0f
#define LARA_HANG_OFFSET 724.0f
#define LARA_WATER_ACCEL 2.0f
#define LARA_SURF_SPEED 15.0f
#define LARA_SWIM_SPEED 50.0f
#define LARA_SWIM_FRICTION 1.0f
#define LARA_WET_SPECULAR 0.5f
#define LARA_WET_TIMER (LARA_WET_SPECULAR / 16.0f) // 4 sec
@ -232,8 +241,8 @@ struct Lara : Character {
for (int i = 0; i < jointsCount - 1; i++) {
TR::Node &t = node[min(i, model->mCount - 2)];
joints[i].posPrev = joints[i].pos = basis.pos;
joints[i].length = t.z;
basis.translate(vec3(0.0f, 0.0f, -t.z));
joints[i].length = float(t.z);
basis.translate(vec3(0.0f, 0.0f, -joints[i].length));
}
joints[jointsCount - 1].posPrev = joints[jointsCount - 1].pos = basis.pos;
joints[jointsCount - 1].length = 1.0f;
@ -397,7 +406,7 @@ struct Lara : Character {
if (level->extra.braid > -1)
braid = new Braid(this, vec3(-4.0f, 24.0f, -48.0f));
/*
/*
pos = vec3(40448, 3584, 60928);
angle = vec3(0.0f, PI * 0.5f, 0.0f);
getEntity().room = 14;
@ -434,12 +443,12 @@ struct Lara : Character {
angle = vec3(0.0f, -PI * 0.25f, 0.0f);
getEntity().room = 13;
// level 2 (room 43)
// level 2 (reach)
pos = vec3(31400, -2560, 25200);
angle = vec3(0.0f, PI, 0.0f);
getEntity().room = 43;
// level 2 (room 16)
// level 2 (hang & climb)
pos = vec3(60907, 0, 39642);
angle = vec3(0.0f, PI * 3 / 2, 0.0f);
getEntity().room = 16;
@ -1074,6 +1083,13 @@ struct Lara : Character {
wpnHide();
}
virtual void cmdJump(const vec3 &vel) {
vec3 v = vel;
if (state == STATE_HANG_UP)
v.y = (3.0f - sqrtf(-2.0f * GRAVITY / 30.0f * (collision.info[Collision::FRONT].floor - pos.y + 800.0f)));
Character::cmdJump(v);
}
void drawGun(int right) {
int mask = right ? BODY_ARM_R3 : BODY_ARM_L3; // unholster
if (layers[1].mask & mask)
@ -1100,22 +1116,21 @@ struct Lara : Character {
bool waterOut() {
// TODO: playSound 36
vec3 dst = pos + getDir() * 32.0f;
if (collision.side != Collision::FRONT || pos.y - collision.info[Collision::FRONT].floor > 256 + 128)
return false;
TR::Level::FloorInfo infoCur, infoDst;
level->getFloorInfo(getEntity().room, (int)pos.x, (int)pos.y, (int)pos.z, infoCur),
level->getFloorInfo(infoCur.roomAbove, (int)dst.x, (int)dst.y, (int)dst.z, infoDst);
vec3 dst = pos + getDir() * (LARA_RADIUS + 32.0f);
int h = int(pos.y - infoDst.floor);
TR::Level::FloorInfo info;
level->getFloorInfo(collision.info[Collision::NONE].roomAbove, int(dst.x), int(dst.y), int(dst.z), info);
if (h >= 0 && h <= 356 && (state == STATE_SURF_TREAD || animation.setState(STATE_SURF_TREAD)) && animation.setState(STATE_STOP)) { // possibility check
alignToWall(-96.0f);
pos.y = float(infoDst.floor);
//pos = dst; // set new position
int h = int(pos.y - info.floor);
if (h >= 0 && h <= (256 + 128) && (state == STATE_SURF_TREAD || animation.setState(STATE_SURF_TREAD)) && animation.setState(STATE_STOP)) {
alignToWall(LARA_RADIUS);
getEntity().room = collision.info[Collision::NONE].roomAbove;
pos.y = float(info.floor);
specular = LARA_WET_SPECULAR;
getEntity().room = infoCur.roomAbove;
updateEntity();
return true;
}
@ -1353,12 +1368,35 @@ struct Lara : Character {
velocity.x = velocity.z = 0.0f;
if ((state == STATE_REACH || state == STATE_UP_JUMP) && (input & ACTION) && emptyHands()) {
if (state == STATE_REACH && velocity.y < 0.0f)
return state;
vec3 p = pos;
Collision c = Collision(level, getRoomIndex(), p, getDir() * 32.0f, vec3(0.0f), LARA_RADIUS, angleExt, 0, 0, 0, 0);
if (c.side != Collision::FRONT)
return state;
Box bounds = animation.getBoundingBox(pos, 0);
int floor = c.info[Collision::FRONT].floor;
int hands = int(bounds.min.y);
if (abs(floor - hands) < 32) {
alignToWall(-LARA_RADIUS);
pos.y = float(floor + LARA_HANG_OFFSET);
stand = STAND_HANG;
updateEntity();
if (state == STATE_REACH) {
return STATE_HANG; // TODO: ANIM_HANG_WALL / ANIM_HANG_NOWALL
} else
return animation.setAnim(ANIM_HANG, -15);
}
/*
vec3 p = pos + getDir() * 128.0f;
TR::Level::FloorInfo info;
// TODO: use brain
@ -1372,7 +1410,7 @@ struct Lara : Character {
} while (info.ceiling > bounds.min.y && info.roomAbove != 0xFF);
if (abs(info.floor - int(bounds.min.y)) < 16) { // reach fall
alignToWall(96.0f);
alignToWall(LARA_RADIUS);
pos.y = info.floor + 724.0f;
updateEntity();
@ -1382,6 +1420,7 @@ struct Lara : Character {
} else
return animation.setAnim(ANIM_HANG, -15);
}
*/
}
if (state == STATE_FORWARD_JUMP) {
@ -1409,7 +1448,7 @@ struct Lara : Character {
if ((dx <= (512 + 128) && dz <= (512 - 128)) ||
(dx <= (512 - 128) && dz <= (512 + 128))) {
alignToWall();
alignToWall(-LARA_RADIUS);
Block *block = (Block*)e.controller;
block->angle.y = angle.y;
@ -1427,26 +1466,22 @@ struct Lara : Character {
if ((input & ACTION) && emptyHands() && doPickUp())
return STATE_PICK_UP;
if ((input & (FORTH | ACTION)) == (FORTH | ACTION) && (animation.index == ANIM_STAND || animation.index == ANIM_STAND_NORMAL) && emptyHands()) { // TODO: get rid of animation.index
vec3 p = pos + getDir() * 64.0f;
TR::Level::FloorInfo info;
level->getFloorInfo(getRoomIndex(), (int)p.x, (int)p.y, (int)p.z, info);
int h = (int)pos.y - info.floor;
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 h = (int)pos.y - floor;
int aIndex = animation.index;
if (info.floor == info.ceiling || h < 256 + 128) {
; // do nothing
} else if (h <= 2 * 256 + 128) {
if (h <= 2 * 256 + 128) {
aIndex = ANIM_CLIMB_2;
pos.y = info.floor + 512.0f;
pos.y = floor + 512.0f;
} else if (h <= 3 * 256 + 128) {
aIndex = ANIM_CLIMB_3;
pos.y = info.floor + 768.0f;
pos.y = floor + 768.0f;
} else if (h <= 7 * 256 + 128)
aIndex = ANIM_CLIMB_JUMP;
if (aIndex != animation.index) {
alignToWall(96.0f);
alignToWall(-LARA_RADIUS);
return animation.setAnim(aIndex);
}
}
@ -1490,7 +1525,7 @@ struct Lara : Character {
int pushState = (input & FORTH) ? STATE_PUSH_BLOCK : STATE_PULL_BLOCK;
Block *block = getBlock();
if (animation.canSetState(pushState) && block->doMove((input & FORTH) != 0)) {
alignToWall(128.0f);
alignToWall(-LARA_RADIUS);
return pushState;
}
}
@ -1553,9 +1588,9 @@ struct Lara : Character {
if (input & FORTH) {
// possibility check
TR::Level::FloorInfo info;
vec3 p = pos + getDir() * 128.0f;
vec3 p = pos + getDir() * (LARA_RADIUS + 2.0f);
level->getFloorInfo(getRoomIndex(), (int)p.x, (int)p.y, (int)p.z, info);
if (info.floor - info.ceiling >= 768)
if (info.floor - info.ceiling >= LARA_HEIGHT)
return (input & WALK) ? STATE_HANDSTAND : STATE_HANG_UP;
}
return STATE_HANG;
@ -1568,6 +1603,8 @@ struct Lara : Character {
if (state == STATE_FORWARD_JUMP || state == STATE_UP_JUMP || state == STATE_BACK_JUMP || state == STATE_LEFT_JUMP || state == STATE_RIGHT_JUMP || state == STATE_FALL || state == STATE_REACH) {
game->waterDrop(pos, 256.0f, 0.2f);
Sprite::add(game, TR::Entity::WATER_SPLASH, getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z);
pos.y += 100.0f;
angle.x = -45.0f * DEG2RAD;
return animation.setAnim(ANIM_WATER_FALL); // TODO: wronng animation
}
@ -1577,10 +1614,13 @@ struct Lara : Character {
Sprite::add(game, TR::Entity::WATER_SPLASH, getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z);
return STATE_DIVE;
}
if (input & JUMP) return STATE_SWIM;
if (state == STATE_GLIDE && speed < LARA_SWIM_SPEED * 2.0f / 3.0f)
return STATE_TREAD;
return (state == STATE_SWIM || velocity.y > GLIDE_SPEED) ? STATE_GLIDE : STATE_TREAD;
return STATE_GLIDE;
}
virtual int getStateOnwater() {
@ -1709,9 +1749,9 @@ struct Lara : Character {
if (state == STATE_SWIM || state == STATE_GLIDE)
w *= TURN_WATER_FAST;
else if (state == STATE_TREAD || state == STATE_SURF_TREAD || state == STATE_SURF_SWIM || state == STATE_SURF_BACK)
w *= TURN_WATER_SLOW;
w *= TURN_WATER_FAST;
else if (state == STATE_RUN)
w *= sign(w) != sign(tilt) ? 0.0f : w * TURN_FAST * tilt / TILT_MAX;
w *= sign(w) != sign(tilt) ? 0.0f : w * TURN_FAST * tilt / LARA_TILT_MAX;
else if (state == STATE_FAST_TURN)
w *= TURN_FAST;
else if (state == STATE_FAST_BACK)
@ -1723,17 +1763,12 @@ struct Lara : Character {
else
w = 0.0f;
if (w != 0.0f) {
w *= Core::deltaTime;
angle.y += w;
velocity = velocity.rotateY(-w); // in-air velocity rotation control
}
if (w != 0.0f)
rotateY(w * Core::deltaTime);
// pitch (underwater only)
if (stand == STAND_UNDERWATER && (input & (FORTH | BACK)) ) {
angle.x += ((input & FORTH) ? -TURN_WATER_SLOW : TURN_WATER_SLOW) * 0.5f * Core::deltaTime;
angle.x = clamp(angle.x, -PI * 0.5f, PI * 0.5f);
}
if (stand == STAND_UNDERWATER && (input & (FORTH | BACK)))
rotateX(((input & FORTH) ? -TURN_WATER_SLOW : TURN_WATER_SLOW) * Core::deltaTime);
// get animation direction
angleExt = angle.y;
@ -1743,6 +1778,7 @@ struct Lara : Character {
case STATE_BACK_JUMP :
case STATE_FAST_BACK :
case STATE_SLIDE_BACK :
case STATE_ROLL_1 :
angleExt += PI;
break;
case STATE_LEFT_JUMP :
@ -1768,18 +1804,15 @@ struct Lara : Character {
case STAND_HANG :
case STAND_ONWATER : {
float speed = 0.0f;
switch (state) {
case STATE_SURF_SWIM :
case STATE_SURF_BACK :
case STATE_SURF_LEFT :
case STATE_SURF_RIGHT :
speed = 15.0f;
case STATE_SURF_RIGHT :
speed = min(speed + 30.0f * LARA_WATER_ACCEL * Core::deltaTime, LARA_SURF_SPEED);
break;
default :
speed = animation.getSpeed();
if (animation.index == ANIM_STAND_ROLL_END)
speed = -speed;
}
if (stand == STAND_ONWATER) {
@ -1790,9 +1823,9 @@ struct Lara : Character {
TR::Entity &e = getEntity();
TR::Level::FloorInfo info;
if (stand == STAND_HANG) {
vec3 p = pos + getDir() * 128.0f;
vec3 p = pos + getDir() * (LARA_RADIUS + 2.0f);
level->getFloorInfo(e.room, (int)p.x, (int)p.y, (int)p.z, info);
if (info.roomAbove != 0xFF && info.floor >= e.y - LARA_HANG_OFFSET)
if (info.roomAbove != TR::NO_ROOM && info.floor >= e.y - LARA_HANG_OFFSET)
level->getFloorInfo(info.roomAbove, (int)p.x, (int)p.y, (int)p.z, info);
} else
level->getFloorInfo(e.room, e.x, e.y, e.z, info);
@ -1803,16 +1836,13 @@ struct Lara : Character {
break;
}
case STAND_UNDERWATER : {
float speed = 0.0f;
if (animation.index == ANIM_TO_UNDERWATER)
speed = 15.0f;
if (state == STATE_SWIM)
speed = SWIM_SPEED;
if (speed != 0.0f)
velocity = vec3(angle.x, angle.y) * speed;
else
velocity = velocity - velocity * min(1.0f, Core::deltaTime * 2.0f);
speed = min(speed + 30.0f * LARA_WATER_ACCEL * Core::deltaTime, LARA_SWIM_SPEED);
if (state == STATE_TREAD || state == STATE_GLIDE)
speed = max(speed - 30.0f * LARA_SWIM_FRICTION * Core::deltaTime, 0.0f);
velocity = vec3(angle.x, angle.y) * speed;
// TODO: apply flow velocity
break;
}
@ -1820,9 +1850,13 @@ struct Lara : Character {
}
virtual void updatePosition() { // TODO: sphere / bbox collision
updateTilt(state == STATE_RUN, TILT_SPEED, TILT_MAX);
// tilt control
vec2 vTilt(LARA_TILT_SPEED * Core::deltaTime, LARA_TILT_MAX);
if (stand == STAND_UNDERWATER)
vTilt *= 2.0f;
updateTilt(state == STATE_RUN || stand == STAND_UNDERWATER, vTilt.x, vTilt.y);
if (velocity.length() >= 0.001f)
if (velocity.length() >= 1.0f)
move();
if (getEntity().type != TR::Entity::LARA) {
@ -1844,11 +1878,11 @@ struct Lara : Character {
}
void move() {
TR::Entity &e = getEntity();
TR::Level::FloorInfo info;
//TR::Entity &e = getEntity();
//TR::Level::FloorInfo info;
float f, c;
bool canPassGap = true;
//float f, c;
//bool canPassGap = true;
/*
if (velocity != 0.0f) {
vec3 dir = velocity.normal() * 128.0f;
@ -1868,11 +1902,53 @@ struct Lara : Character {
}
}
*/
vec3 offset = velocity * Core::deltaTime * 30.0f;
vec3 vel = velocity * Core::deltaTime * 30.0f;
vec3 opos(pos), offset(0.0f);
vec3 p = pos;
pos = pos + offset;
float radius = stand == STAND_UNDERWATER ? LARA_RADIUS_WATER : LARA_RADIUS;
int maxHeight = stand == STAND_UNDERWATER ? LARA_HEIGHT_WATER : LARA_HEIGHT;
int minHeight = 0;
int maxAscent = 256 + 128;
int maxDescent = 0xFFFFFF;
if (state == STATE_WALK || state == STATE_BACK)
maxDescent = maxAscent;
if (state == STATE_STEP_LEFT || state == STATE_STEP_RIGHT)
maxAscent = maxDescent = 64;
if (stand == STAND_ONWATER)
maxAscent = -1;
if (stand == STAND_HANG) {
maxHeight = 0;
maxAscent = maxDescent = 64;
offset = getDir() * (LARA_RADIUS + 32.0f);
offset.y -= LARA_HANG_OFFSET;
}
if (stand == STAND_UNDERWATER) {
offset.y += LARA_HEIGHT_WATER * 0.5f;
}
int room = getRoomIndex();
collision = Collision(level, room, pos, offset, vel, radius, angleExt, minHeight, maxHeight, maxAscent, maxDescent);
if (stand != STAND_HANG && (collision.side == Collision::LEFT || collision.side == Collision::RIGHT)) {
float rot = TURN_WALL_Y * Core::deltaTime;
rotateY((collision.side == Collision::LEFT) ? rot : -rot);
}
if (stand == STAND_HANG && collision.side != Collision::FRONT) {
offset.x = offset.z = 0.0f;
minHeight = LARA_HANG_OFFSET;
maxDescent = 0xFFFFFF;
maxAscent = -LARA_HANG_OFFSET;
vec3 p = pos;
collision = Collision(level, room, p, offset, vec3(0.0f), radius, angleExt, minHeight, maxHeight, maxAscent, maxDescent);
if (collision.side == Collision::FRONT)
pos = opos;
}
/*
if (canPassGap) {
level->getFloorInfo(e.room, (int)pos.x, (int)pos.y, (int)pos.z, info);
canPassGap = (info.floor - info.ceiling) >= (stand == STAND_GROUND ? 768 : 512);
@ -1880,6 +1956,8 @@ struct Lara : Character {
f = info.floor - pos.y;
c = pos.y - info.ceiling;
*/
/*
TR::Animation *anim = animation;
Box eBox = Box(pos - vec3(128.0f, 0.0f, 128.0f), pos + vec3(128.0, getHeight(), 128.0f)); // getBoundingBox();
@ -1915,6 +1993,7 @@ struct Lara : Character {
}
}
*/
/*
if (canPassGap)
switch (stand) {
case STAND_AIR : {
@ -1959,38 +2038,46 @@ struct Lara : Character {
break;
default : ;
}
*/
// get current leading foot in animation
int rightStart = 0;
if (state == STATE_RUN) rightStart = 6;
if (state == STATE_WALK) rightStart = 13;
if (state == STATE_BACK) rightStart = 28;
bool isLeftFoot = animation.frameIndex < rightStart || animation.frameIndex > (rightStart + animation.framesCount / 2);
if (!canPassGap) {
pos = p; // TODO: use smart ejection
if (stand == STAND_UNDERWATER) {
if (collision.side == Collision::TOP)
rotateX(-TURN_WALL_X * Core::deltaTime);
if (collision.side == Collision::BOTTOM)
rotateX( TURN_WALL_X * Core::deltaTime);
}
if (stand == STAND_AIR && collision.side == Collision::TOP && velocity.y < 0.0f)
velocity.y = 30.0f;
if (collision.side == Collision::FRONT) {
int floor = collision.info[Collision::FRONT].floor;
// hit the wall
switch (stand) {
case STAND_AIR :
if (state == STATE_UP_JUMP || state == STATE_REACH) {
if (state == STATE_UP_JUMP || state == STATE_REACH)
velocity.x = velocity.z = 0.0f;
if (c <= 0 && offset.y < 0.0f) offset.y = velocity.y = 0.0f;
}
if (velocity.x != 0.0f || velocity.z != 0.0f) {
animation.setAnim(ANIM_SMASH_JUMP);
velocity.x = -velocity.x * 0.5f;
velocity.z = -velocity.z * 0.5f;
if (offset.y < 0.0f)
offset.y = velocity.y = 0.0f;
velocity.x = -velocity.x * 0.25f;
velocity.z = -velocity.z * 0.25f;
if (velocity.y < 0.0f)
velocity.y = 30.0f;
}
pos.y = p.y + offset.y;
break;
case STAND_GROUND :
case STAND_HANG :
if (f <= -(256 * 3 - 128) && state == STATE_RUN)
if (opos.y - floor > (256 * 3 - 128) && state == STATE_RUN)
animation.setAnim(isLeftFoot ? ANIM_SMASH_RUN_LEFT : ANIM_SMASH_RUN_RIGHT);
else if (stand == STAND_HANG)
animation.setAnim(ANIM_HANG, -21);
@ -1998,11 +2085,18 @@ struct Lara : Character {
animation.setAnim((state == STATE_RUN || state == STATE_WALK) ? (isLeftFoot ? ANIM_STAND_LEFT : ANIM_STAND_RIGHT) : ANIM_STAND);
velocity.x = velocity.z = 0.0f;
break;
case STAND_UNDERWATER :
if (fabsf(angle.x) > TURN_WALL_X_CLAMP)
rotateX(TURN_WALL_X * Core::deltaTime * sign(angle.x));
else
pos.y = opos.y;
break;
default : ;// no smash animation
}
} else
} else {
if (stand == STAND_GROUND) {
int h = int(info.floor - pos.y);
int floor = collision.info[Collision::NONE].floor;
int h = int(floor - opos.y);
if (h >= 256 && state == STATE_FAST_BACK) {
stand = STAND_AIR;
@ -2010,18 +2104,20 @@ struct Lara : Character {
} else if (h >= 128 && (state == STATE_WALK || state == STATE_BACK)) { // descend
if (state == STATE_WALK) animation.setAnim(isLeftFoot ? ANIM_WALK_DESCEND_LEFT : ANIM_WALK_DESCEND_RIGHT);
if (state == STATE_BACK) animation.setAnim(isLeftFoot ? ANIM_BACK_DESCEND_LEFT : ANIM_BACK_DESCEND_RIGHT);
pos.y = float(info.floor);
pos.y = float(floor);
} else if (h > -1.0f) {
pos.y = min((float)info.floor, pos.y += DESCENT_SPEED * Core::deltaTime);
pos.y = min(float(floor), pos.y += DESCENT_SPEED * Core::deltaTime);
} else if (h > -128) {
pos.y = float(info.floor);
pos.y = float(floor);
} else if (h >= -(256 + 128) && (state == STATE_RUN || state == STATE_WALK)) { // ascend
if (state == STATE_RUN) animation.setAnim(isLeftFoot ? ANIM_RUN_ASCEND_LEFT : ANIM_RUN_ASCEND_RIGHT);
if (state == STATE_WALK) animation.setAnim(isLeftFoot ? ANIM_WALK_ASCEND_LEFT : ANIM_WALK_ASCEND_RIGHT);
pos.y = float(info.floor);
pos.y = float(floor);
} else
pos.y = float(info.floor);
pos.y = float(floor);
}
collision.side = Collision::NONE;
}
updateEntity();
checkRoom();

View File

@ -767,7 +767,8 @@ struct Level : IGame {
delete cube;
delete mesh;
delete camera;
delete camera;
Sound::stopAll();
}
void initTextures() {
@ -1148,7 +1149,7 @@ struct Level : IGame {
}
}
}
}
}
camera->update();
waterCache->update();
@ -1375,6 +1376,40 @@ struct Level : IGame {
// renderModel(level.models[modelIndex], level.entities[4]);
*/
Debug::begin();
lara->updateEntity(); // TODO clip angle while rotating
int q = int(normalizeAngle(lara->angleExt + PI * 0.25f) / (PI * 0.5f));
float radius = 256.0f;
const vec2 v[] = {
{ -radius, radius },
{ radius, radius },
{ radius, -radius },
{ -radius, -radius },
};
const vec2 &l = v[q],
&r = v[(q + 1) % 4],
&f = (q %= 2) ? vec2(l.x, radius * cosf(lara->angleExt)) : vec2(radius * sinf(lara->angleExt), l.y);
vec3 F = vec3(f.x, 0.0f, f.y);
vec3 L = vec3(l.x, 0.0f, l.y);
vec3 R = vec3(r.x, 0.0f, r.y);
vec3 p, n = lara->pos + vec3(0.0f, -512.0f, 0.0f);
Core::setDepthTest(false);
glBegin(GL_LINES);
glColor3f(0, 0, 1); p = n; glVertex3fv((GLfloat*)&p); p += F; glVertex3fv((GLfloat*)&p);
glColor3f(1, 0, 0); p = n; glVertex3fv((GLfloat*)&p); p += L; glVertex3fv((GLfloat*)&p);
glColor3f(0, 1, 0); p = n; glVertex3fv((GLfloat*)&p); p += R; glVertex3fv((GLfloat*)&p);
glColor3f(1, 1, 0); p = lara->pos; glVertex3fv((GLfloat*)&p); p -= vec3(0.0f, LARA_HANG_OFFSET, 0.0f); glVertex3fv((GLfloat*)&p);
glEnd();
Core::setDepthTest(true);
/*
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
@ -1415,15 +1450,17 @@ struct Level : IGame {
glPopMatrix();
*/
Core::setBlending(bmAlpha);
// Debug::Level::rooms(level, lara->pos, lara->getEntity().room);
// Debug::Level::lights(level, lara->getRoomIndex());
// Debug::Level::sectors(level, lara->getRoomIndex(), (int)lara->pos.y);
Debug::Level::sectors(level, lara->getRoomIndex(), (int)lara->pos.y);
// Core::setDepthTest(false);
// Debug::Level::portals(level);
// Core::setDepthTest(true);
// Debug::Level::meshes(level);
// Debug::Level::entities(level);
Core::setBlending(bmNone);
/*
static int dbg_ambient = 0;
dbg_ambient = int(params.time * 2) % 4;

View File

@ -124,6 +124,8 @@
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>
Change view: V<br>
Time Control: R - slow motion, T - fast motion<br>
FullScreen: Alt + Enter
</span>

View File

@ -189,6 +189,7 @@
<ClInclude Include="..\..\animation.h" />
<ClInclude Include="..\..\camera.h" />
<ClInclude Include="..\..\character.h" />
<ClInclude Include="..\..\collision.h" />
<ClInclude Include="..\..\controller.h" />
<ClInclude Include="..\..\core.h" />
<ClInclude Include="..\..\debug.h" />

View File

@ -530,6 +530,12 @@ namespace Sound {
delete stream;
return NULL;
}
void stopAll() {
for (int i = 0; i < channelsCount; i++)
delete channels[i];
channelsCount = 0;
}
}
#endif

View File

@ -85,6 +85,16 @@ float shortAngle(float a, float b) {
return clampAngle(n - int(n / PI2) * PI2);
}
float normalizeAngle(float angle) {
while (angle < 0.0f) angle += PI2;
while (angle > PI2) angle -= PI2;
return angle;
}
int angleQuadrant(float angle) {
return int(normalizeAngle(angle + PI * 0.25f) / (PI * 0.5f));
}
float decrease(float delta, float &value, float &speed) {
if (speed > 0.0f && fabsf(delta) > 0.01f) {
if (delta > 0) speed = min(delta, speed);
@ -112,11 +122,13 @@ struct vec2 {
vec2(float s) : x(s), y(s) {}
vec2(float x, float y) : x(x), y(y) {}
float& operator [] (int index) const { ASSERT(index >= 0 && index <= 1); return ((float*)this)[index]; }
inline float& operator [] (int index) const { ASSERT(index >= 0 && index <= 1); return ((float*)this)[index]; }
bool operator == (float s) const { return x == s && y == s; }
bool operator != (float s) const { return !(*this == s); }
vec2 operator - () const { return vec2(-x, -y); }
inline bool operator == (const vec2 &v) const { return x == v.x && y == v.y; }
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 !(*this == s); }
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; }
@ -138,6 +150,7 @@ struct vec2 {
float length2() const { return dot(*this); }
float length() const { return sqrtf(length2()); }
vec2 normal() const { float s = length(); return s == 0.0 ? (*this) : (*this)*(1.0f/s); }
float angle() const { return atan2(y, x); }
};
struct vec3 {
@ -148,11 +161,13 @@ struct vec3 {
vec3(const vec2 &xy, float z = 0.0f) : x(xy.x), y(xy.y), z(z) {}
vec3(float lng, float lat) : x(sinf(lat) * cosf(lng)), y(-sinf(lng)), z(cosf(lat) * cosf(lng)) {}
float& operator [] (int index) const { ASSERT(index >= 0 && index <= 2); return ((float*)this)[index]; }
inline float& operator [] (int index) const { ASSERT(index >= 0 && index <= 2); 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 { return vec3(-x, -y, -z); }
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 !(*this == v); }
inline bool operator == (float s) const { return x == s && y == s && z == s; }
inline bool operator != (float s) const { return !(*this == s); }
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; }
@ -598,6 +613,13 @@ struct Basis {
void rotate(const quat &q) {
rot = rot * q;
}
Basis lerp(const Basis &basis, float t) {
Basis b;
b.rot = rot.lerp(basis.rot, t);
b.pos = pos.lerp(basis.pos, t);
return b;
}
};
struct ubyte2 {