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

Merge remote-tracking branch 'refs/remotes/XProger/master'

This commit is contained in:
Gh0stBlade
2017-09-15 09:52:41 +01:00
59 changed files with 4169 additions and 1594 deletions

View File

@@ -10,3 +10,9 @@ inspired by OpenTomb project http://opentomb.github.io/
## Links
* [Discord channel](https://discord.gg/EF8JaQB)
* [Tomb Raider Forums thread](http://www.tombraiderforums.com/showthread.php?t=216618)
## Screenshots
![Waterfall](http://xproger.info/projects/OpenLara/shots/waterfall.jpg)
![Double-aim](http://xproger.info/projects/OpenLara/shots/multi-aim.jpg)
![Cutscene](http://xproger.info/projects/OpenLara/shots/cut1.jpg)
![Cistern](http://xproger.info/projects/OpenLara/shots/flipmap.jpg)

Binary file not shown.

Binary file not shown.

View File

@@ -1 +0,0 @@
OpenLara CUT1.PHD 008.ogg

Binary file not shown.

Binary file not shown.

BIN
bin/audio/track_02.ogg Normal file

Binary file not shown.

BIN
bin/audio/track_03.ogg Normal file

Binary file not shown.

BIN
bin/audio/track_04.ogg Normal file

Binary file not shown.

BIN
bin/audio/track_09.ogg Normal file

Binary file not shown.

BIN
bin/audio/track_12.ogg Normal file

Binary file not shown.

BIN
bin/audio/track_13.ogg Normal file

Binary file not shown.

BIN
bin/level/TITLE.PSX Normal file

Binary file not shown.

BIN
bin/level/TITLEH.PCX Normal file

Binary file not shown.

View File

@@ -194,7 +194,7 @@ struct Animation {
if (flip) {
int frame = (*ptr++) - anim->frameStart;
int fx = (*ptr++) & 0x3FFF;
*flip = fx == TR::EFFECT_ROTATE_180 && frame == frameIndex;
*flip = fx == TR::Effect::ROTATE_180 && frame == frameIndex;
} else
ptr += 2;
break;

View File

@@ -34,10 +34,9 @@ const char GUI[] =
struct ShaderCache {
enum Effect { FX_NONE = 0, FX_UNDERWATER = 1, FX_ALPHA_TEST = 2, FX_CLIP_PLANE = 4 };
IGame *game;
Shader *shaders[Core::passMAX][Shader::MAX][(FX_UNDERWATER | FX_ALPHA_TEST | FX_CLIP_PLANE) + 1];
ShaderCache(IGame *game) : game(game) {
ShaderCache() {
memset(shaders, 0, sizeof(shaders));
LOG("shader: cache warm up...\n");
@@ -97,7 +96,7 @@ struct ShaderCache {
~ShaderCache() {
for (int pass = 0; pass < Core::passMAX; pass++)
for (int type = 0; type < Shader::MAX; type++)
for (int fx = 0; fx < sizeof(shaders[Core::passMAX][Shader::MAX]) / sizeof(shaders[Core::passMAX][Shader::MAX][FX_NONE]); fx++)
for (int fx = 0; fx < sizeof(shaders[pass][Shader::MAX]) / sizeof(shaders[pass][Shader::MAX][FX_NONE]); fx++)
delete shaders[pass][type][fx];
}
@@ -118,7 +117,7 @@ struct ShaderCache {
}
}
const char *passNames[] = { "COMPOSE", "SHADOW", "AMBIENT", "WATER", "FILTER", "VOLUME", "GUI" };
const char *passNames[] = { "COMPOSE", "SHADOW", "AMBIENT", "WATER", "FILTER", "GUI" };
const char *src = NULL;
const char *typ = NULL;
switch (pass) {
@@ -128,10 +127,8 @@ struct ShaderCache {
static const char *typeNames[] = { "SPRITE", "FLASH", "ROOM", "ENTITY", "MIRROR" };
src = SHADER;
typ = typeNames[type];
int animTexRangesCount = game->getMesh()->animTexRangesCount;
int animTexOffsetsCount = game->getMesh()->animTexOffsetsCount;
sprintf(def, "%s#define PASS_%s\n#define TYPE_%s\n#define MAX_LIGHTS %d\n#define MAX_RANGES %d\n#define MAX_OFFSETS %d\n#define MAX_CONTACTS %d\n#define FOG_DIST (1.0/%d.0)\n#define WATER_FOG_DIST (1.0/%d.0)\n#define SHADOW_TEX_SIZE %d.0\n", ext, passNames[pass], typ, MAX_LIGHTS, animTexRangesCount, animTexOffsetsCount, MAX_CONTACTS, FOG_DIST, WATER_FOG_DIST, SHADOW_TEX_SIZE);
typ = typeNames[type];
sprintf(def, "%s#define PASS_%s\n#define TYPE_%s\n#define MAX_LIGHTS %d\n#define MAX_RANGES %d\n#define MAX_OFFSETS %d\n#define MAX_CONTACTS %d\n#define FOG_DIST (1.0/%d.0)\n#define WATER_FOG_DIST (1.0/%d.0)\n#define SHADOW_TEX_SIZE %d.0\n", ext, passNames[pass], typ, MAX_LIGHTS, MAX_ANIM_TEX_RANGES, MAX_ANIM_TEX_OFFSETS, MAX_CONTACTS, FOG_DIST, WATER_FOG_DIST, SHADOW_TEX_SIZE);
if (fx & FX_UNDERWATER) strcat(def, "#define UNDERWATER\n" UNDERWATER_COLOR);
if (fx & FX_ALPHA_TEST) strcat(def, "#define ALPHA_TEST\n");
if (fx & FX_CLIP_PLANE) strcat(def, "#define CLIP_PLANE\n");
@@ -172,7 +169,7 @@ struct ShaderCache {
return shaders[pass][type][fx] = new Shader(src, def);
}
void bind(Core::Pass pass, Shader::Type type, int fx) {
void bind(Core::Pass pass, Shader::Type type, int fx, IGame *game) {
Core::pass = pass;
Shader *shader = shaders[pass][type][fx];
if (!shader)
@@ -185,6 +182,8 @@ struct ShaderCache {
shader->setParam(uViewPos, Core::viewPos);
shader->setParam(uParam, Core::params);
MeshBuilder *mesh = game->getMesh();
ASSERT(mesh->animTexRangesCount <= MAX_ANIM_TEX_RANGES);
ASSERT(mesh->animTexOffsetsCount <= MAX_ANIM_TEX_OFFSETS);
shader->setParam(uAnimTexRanges, mesh->animTexRanges[0], mesh->animTexRangesCount);
shader->setParam(uAnimTexOffsets, mesh->animTexOffsets[0], mesh->animTexOffsetsCount);
}
@@ -205,6 +204,7 @@ struct AmbientCache {
struct Task {
int room;
int flip;
int sector;
Cube *cube;
} tasks[32];
@@ -242,6 +242,7 @@ struct AmbientCache {
Task &task = tasks[tasksCount++];
task.room = room;
task.flip = level->isFlipped;
task.sector = sector;
task.cube = &items[offsets[room] + sector];
task.cube->status = Cube::WAIT;
@@ -288,11 +289,16 @@ struct AmbientCache {
Core::setDepthTest(true);
}
void precessQueue() {
void processQueue() {
game->setupBinding();
for (int i = 0; i < tasksCount; i++) {
Task &task = tasks[i];
bool oldFlip = level->isFlipped;
level->isFlipped = task.flip != 0;
renderAmbient(task.room, task.sector, &task.cube->colors[0]);
level->isFlipped = oldFlip;
task.cube->status = Cube::READY;
}
tasksCount = 0;
@@ -321,7 +327,7 @@ struct AmbientCache {
};
struct WaterCache {
#define MAX_SURFACES 8
#define MAX_SURFACES 16
#define MAX_INVISIBLE_TIME 5.0f
#define SIMULATE_TIMESTEP (1.0f / 40.0f)
#define DETAIL (64.0f / 1024.0f)
@@ -355,7 +361,7 @@ struct WaterCache {
TR::Level *level = game->getLevel();
TR::Room &r = level->rooms[to]; // underwater room
int minX = r.xSectors, minZ = r.zSectors, maxX = 0, maxZ = 0, posY;
int minX = r.xSectors, minZ = r.zSectors, maxX = 0, maxZ = 0, posY = 0;
for (int z = 0; z < r.zSectors; z++)
for (int x = 0; x < r.xSectors; x++) {
@@ -402,9 +408,9 @@ struct WaterCache {
size = vec3(float((maxX - minX) * 512), 1.0f, float((maxZ - minZ) * 512)); // half size
pos = vec3(r.info.x + minX * 1024 + size.x, float(posY), r.info.z + minZ * 1024 + size.z);
data[0] = new Texture(w * 64, h * 64, Texture::RGBA_HALF, false);
data[1] = new Texture(w * 64, h * 64, Texture::RGBA_HALF, false);
caustics = new Texture(512, 512, Texture::RGB16, false);
data[0] = new Texture(w * 64, h * 64, Texture::RGBA_HALF);
data[1] = new Texture(w * 64, h * 64, Texture::RGBA_HALF);
caustics = new Texture(512, 512, Texture::RGB16);
mask = new Texture(w, h, Texture::RGB16, false, m, false);
delete[] m;
@@ -413,6 +419,7 @@ struct WaterCache {
// texture may be initialized with trash, so...
Core::setTarget(data[0], true);
Core::validateRenderState(); // immediate clear
Core::invalidateTarget(false, true);
}
void free() {
@@ -592,6 +599,8 @@ struct WaterCache {
}
void renderMask() {
if (!visible) return;
PROFILE_MARKER("WATER_RENDER_MASK");
// mask underwater geometry by zero alpha
game->setShader(Core::passWater, Shader::WATER_MASK);
Core::active.shader->setParam(uTexParam, vec4(1.0f));
@@ -614,21 +623,38 @@ struct WaterCache {
Core::setCulling(cfFront);
}
void getRefract() {
int w = int(Core::viewportDef.z);
int h = int(Core::viewportDef.w);
void getTargetSize(int &w, int &h) {
if (Core::active.target != NULL) {
w = Core::active.target->width;
h = Core::active.target->height;
} else {
w = int(Core::viewportDef.z);
h = int(Core::viewportDef.w);
}
}
void getRefract() {
if (!visible) return;
PROFILE_MARKER("WATER_REFRACT");
int w, h;
getTargetSize(w, h);
// get refraction texture
if (!refract || w != refract->width || h != refract->height) {
delete refract;
refract = new Texture(w, h, Texture::RGBA, false);
Core::setTarget(refract, true);
Core::validateRenderState(); // immediate clear
Core::invalidateTarget(false, true);
Core::setTarget(NULL);
}
Core::copyTarget(refract, 0, 0, int(Core::viewportDef.x), int(Core::viewportDef.y), w, h); // copy framebuffer into refraction texture
Core::copyTarget(refract, 0, 0, 0, 0, w, h); // copy framebuffer into refraction texture
}
void simulate() {
PROFILE_MARKER("WATER_SIMULATE");
// simulate water
Core::setDepthTest(false);
Core::setBlending(bmNone);
for (int i = 0; i < count; i++) {
Item &item = items[i];
if (!item.visible) continue;
@@ -646,6 +672,7 @@ struct WaterCache {
void renderReflect() {
if (!visible) return;
PROFILE_MARKER("WATER_REFLECT");
for (int i = 0; i < count; i++) {
Item &item = items[i];
@@ -687,22 +714,20 @@ struct WaterCache {
}
void render() {
if (!visible) return;
PROFILE_MARKER("WATER_RENDER");
for (int i = 0; i < count; i++) {
Item &item = items[i];
if (!item.visible) continue;
// render water plane
if (level->rooms[item.from].lightsCount) {
TR::Room::Light &light = level->rooms[item.from].lights[0];
Core::lightPos[0] = vec3(float(light.x), float(light.y), float(light.z));
float lum = intensityf(light.intensity);
Core::lightColor[0] = vec4(lum, lum, lum, float(light.radius) * float(light.radius));
}
game->setShader(Core::passWater, Shader::WATER_COMPOSE);
Core::active.shader->setParam(uLightPos, Core::lightPos[0], 1);
Core::active.shader->setParam(uLightColor, Core::lightColor[0], 1);
Core::active.shader->setParam(uParam, vec4(Core::viewportDef.z / refract->width, Core::viewportDef.w / refract->height, 0.05f, 0.02f));
int w, h;
getTargetSize(w, h);
Core::active.shader->setParam(uParam, vec4(float(w) / refract->width, float(h) / refract->height, 0.05f, 0.02f));
float sx = item.size.x * DETAIL / (item.data[0]->width / 2);
float sz = item.size.z * DETAIL / (item.data[0]->height / 2);

View File

@@ -4,35 +4,53 @@
#include "core.h"
#include "frustum.h"
#include "controller.h"
#include "lara.h"
#include "character.h"
#define CAMERA_OFFSET (1024.0f + 256.0f)
struct Camera : Controller {
Lara *owner;
Frustum *frustum;
struct Camera : ICamera {
enum {
STATE_FOLLOW,
STATE_STATIC,
STATE_LOOK,
STATE_COMBAT,
STATE_CUTSCENE,
STATE_HEAVY
} state;
IGame *game;
TR::Level *level;
Character *owner;
Frustum *frustum;
float fov, znear, zfar;
vec3 target, destPos, lastDest, advAngle;
vec3 target, destPos, lastDest, angle, advAngle;
float advTimer;
mat4 mViewInv;
int room;
float timer;
int actTargetEntity, actCamera;
float shake;
Basis prevBasis;
vec4 *reflectPlane;
bool cutscene;
int viewIndex;
int viewIndexLast;
Controller* viewTarget;
float speed;
bool firstPerson;
bool isVR;
float shake;
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), isVR(false) {
Camera(IGame *game, Character *owner) : ICamera(), game(game), level(game->getLevel()), owner(owner), frustum(new Frustum()), timer(-1.0f), shake(0.0f), viewIndex(-1), viewIndexLast(-1), viewTarget(NULL), isVR(false) {
changeView(false);
cutscene = owner->getEntity().type != TR::Entity::LARA && level->cameraFrames;
if (owner->getEntity().type != TR::Entity::LARA && level->cameraFrames) {
state = STATE_CUTSCENE;
room = level->entities[level->cutEntity].room;
} else
state = STATE_FOLLOW;
advTimer = -1.0f;
}
virtual ~Camera() {
@@ -40,14 +58,23 @@ struct Camera : Controller {
}
virtual int getRoomIndex() const {
return actCamera > -1 ? level->cameras[actCamera].room : room;
return room;
}
virtual void checkRoom() {
TR::Level::FloorInfo info;
level->getFloorInfo(room, (int)pos.x, (int)pos.y, (int)pos.z, info);
if (state == STATE_CUTSCENE) {
for (int i = 0; i < level->roomsCount; i++)
if (owner->insideRoom(pos, i)) {
room = i;
break;
}
return;
}
if (info.roomNext != TR::NO_ROOM)
TR::Level::FloorInfo info;
level->getFloorInfo(getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z, info);
if (info.roomNext != TR::NO_ROOM)
room = info.roomNext;
if (pos.y < info.roomCeiling) {
@@ -67,19 +94,6 @@ struct Camera : Controller {
}
}
virtual bool activate(ActionCommand *cmd) {
Controller::activate(cmd);
this->timer = max(max(1.0f, this->timer), cmd->timer);
if (cmd->action == TR::Action::CAMERA_TARGET)
actTargetEntity = cmd->value;
if (cmd->action == TR::Action::CAMERA_SWITCH) {
actCamera = cmd->value;
lastDest = pos;
}
activateNext();
return true;
}
void updateListener() {
Sound::listener.matrix = mViewInv;
TR::Room &r = level->rooms[getRoomIndex()];
@@ -91,16 +105,54 @@ struct Camera : Controller {
return level->rooms[getRoomIndex()].flags.water;
}
void setView(int viewIndex, float timer, float speed) {
// if (viewIndex == viewIndexLast) return;
viewIndexLast = viewIndex;
state = STATE_STATIC;
this->viewIndex = viewIndex;
this->timer = timer;
this->speed = speed;
lastDest = pos;
if (viewIndex > -1)
room = level->cameras[viewIndex].room;
}
vec3 getViewPoint() {
vec3 p = owner->getViewPoint();
if (owner->stand != Character::STAND_UNDERWATER)
p.y -= 256.0f;
if (state == STATE_COMBAT)
p.y -= 256.0f;
return p;
}
void resetTarget(const vec3 &viewPoint) {
timer = -1.0f;
state = STATE_FOLLOW;
viewTarget = NULL;
viewIndex = -1;
target = viewPoint;
}
virtual void update() {
if (shake > 0.0f)
shake = max(0.0f, shake - Core::deltaTime);
#ifndef LEVEL_EDITOR
if (cutscene) { // cutscene
timer += Core::deltaTime * 30;
if (state == STATE_CUTSCENE) {
timer += Core::deltaTime * 30.0f;
float t = timer - int(timer);
int indexA = int(timer) % level->cameraFramesCount;
int indexB = min(indexA + 1, level->cameraFramesCount - 1);
int indexA = min(int(timer), level->cameraFramesCount - 1);
int indexB = min((indexA + 1), level->cameraFramesCount - 1);
if (indexA == level->cameraFramesCount - 1) {
if (level->cutEntity != -1)
game->loadLevel(TR::LevelID(level->id + 1));
else
state = STATE_FOLLOW;
}
TR::CameraFrame *frameA = &level->cameraFrames[indexA];
TR::CameraFrame *frameB = &level->cameraFrames[indexB];
@@ -119,10 +171,8 @@ struct Camera : Controller {
pos = level->cutMatrix * pos;
target = level->cutMatrix * target;
// TODO: frame->roll
} else
#endif
{
checkRoom();
} else {
vec3 advAngleOld = advAngle;
if (Input::down[ikMouseL]) {
@@ -137,9 +187,7 @@ struct Camera : Controller {
if (advAngleOld == advAngle) {
if (advTimer > 0.0f) {
advTimer -= Core::deltaTime;
if (advTimer <= 0.0f)
advTimer = 0.0f;
advTimer = max(0.0f, advTimer - Core::deltaTime);
}
} else
advTimer = -1.0f;
@@ -147,72 +195,34 @@ struct Camera : Controller {
if (owner->velocity != 0.0f && advTimer < 0.0f && !Input::down[ikMouseL])
advTimer = -advTimer;
#ifndef LEVEL_EDITOR
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);
}
#endif
if (owner->health > 0)
angle = owner->angle + advAngle;
angle = owner->angle + advAngle;
angle.z = 0.0f;
if (owner->stand == Lara::STAND_ONWATER)
if (owner->stand == Character::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)
if (owner->stand == Character::STAND_HANG)
angle.x -= 60.0f * DEG2RAD;
#ifdef LEVEL_EDITOR
angle = advAngle;
angle.x = min(max(angle.x, -80 * DEG2RAD), 80 * DEG2RAD);
vec3 d = vec3(sinf(angle.y) * cosf(angle.x), -sinf(angle.x), cosf(angle.y) * cosf(angle.x));
vec3 v = vec3(0);
Controller *lookAt = viewTarget;
if (state != STATE_STATIC) {
if (owner->viewTarget)
owner->lookAt(lookAt = owner->viewTarget);
else
owner->lookAt(lookAt = viewTarget);
} else
owner->lookAt(NULL);
if (Input::down[ikW]) v = v + d;
if (Input::down[ikS]) v = v - d;
if (Input::down[ikA]) v = v + d.cross(vec3(0, 1, 0));
if (Input::down[ikD]) v = v - d.cross(vec3(0, 1, 0));
pos = pos + v.normal() * (Core::deltaTime * 512.0f * 10.0f);
vec3 viewPoint = getViewPoint();
mViewInv.identity();
mViewInv.translate(pos);
mViewInv.rotateY(angle.y - PI);
mViewInv.rotateX(-angle.x);
mViewInv.rotateZ(PI);
updateListener();
return;
#endif
int lookAt = -1;
if (actTargetEntity > -1) lookAt = actTargetEntity;
if (owner->arms[0].target > -1 && owner->arms[1].target > -1 && owner->arms[0].target != owner->arms[1].target) {
// two diff targets
} else if (owner->arms[0].target > -1)
lookAt = owner->arms[0].target;
else if (owner->arms[1].target > -1)
lookAt = owner->arms[1].target;
else if (owner->arms[0].tracking > -1)
lookAt = owner->arms[0].tracking;
else if (owner->arms[1].tracking > -1)
lookAt = owner->arms[1].tracking;
owner->viewTarget = lookAt;
if (timer > 0.0f) {
timer -= Core::deltaTime;
if (timer <= 0.0f) {
timer = 0.0f;
if (room != getRoomIndex())
pos = lastDest;
actTargetEntity = actCamera = -1;
target = owner->getViewPoint();
}
}
if (firstPerson && actCamera == -1) {
if (firstPerson && viewIndex == -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;
@@ -231,40 +241,46 @@ struct Camera : Controller {
return;
}
float lerpFactor = (lookAt == -1) ? 6.0f : 10.0f;
float lerpFactor = lookAt ? 10.0f : 6.0f;
vec3 dir;
target = target.lerp(owner->getViewPoint(), lerpFactor * Core::deltaTime);
if (actCamera > -1) {
TR::Camera &c = level->cameras[actCamera];
destPos = vec3(float(c.x), float(c.y), float(c.z));
target = target.lerp(viewPoint, lerpFactor * Core::deltaTime);
if (viewIndex > -1) {
TR::Camera &cam = level->cameras[viewIndex];
destPos = vec3(float(cam.x), float(cam.y), float(cam.z));
if (room != getRoomIndex())
pos = destPos;
if (lookAt > -1) {
TR::Entity &e = level->entities[lookAt];
target = ((Controller*)e.controller)->pos;
}
if (lookAt)
target = lookAt->pos;
} else {
if (lookAt > -1) {
TR::Entity &e = level->entities[lookAt];
dir = (((Controller*)e.controller)->pos - target).normal();
if (lookAt) {
dir = (lookAt->pos - target).normal();
} else
dir = getDir();
dir = vec3(angle.x, angle.y);
int destRoom;
if ((!owner->emptyHands() || owner->state != Lara::STATE_BACK_JUMP) || lookAt > -1) {
if ((state == STATE_COMBAT || owner->state != 25) || lookAt) { // TODO: FUUU! 25 == Lara::STATE_BACK_JUMP
vec3 eye = target - dir * CAMERA_OFFSET;
destPos = trace(owner->getRoomIndex(), target, eye, destRoom, true);
destPos = owner->trace(owner->getRoomIndex(), target, eye, destRoom, true);
lastDest = destPos;
} else {
vec3 eye = lastDest + dir.cross(vec3(0, 1, 0)).normal() * 2048.0f - vec3(0.0f, 512.0f, 0.0f);
destPos = trace(owner->getRoomIndex(), target, eye, destRoom, true);
}
destPos = owner->trace(owner->getRoomIndex(), target, eye, destRoom, true);
}
room = destRoom;
}
pos = pos.lerp(destPos, Core::deltaTime * lerpFactor);
if (actCamera <= -1)
if (timer > 0.0f) {
timer -= Core::deltaTime;
if (timer <= 0.0f)
resetTarget(viewPoint);
} else
resetTarget(target);
if (viewIndex == -1)
checkRoom();
}
@@ -314,7 +330,7 @@ struct Camera : Controller {
room = owner->getRoomIndex();
pos = owner->pos - owner->getDir() * 1024.0f;
target = owner->getViewPoint();
target = getViewPoint();
advAngle = vec3(0.0f);
advTimer = 0.0f;

View File

@@ -2,7 +2,6 @@
#define H_CHARACTER
#include "controller.h"
#include "trigger.h"
struct Character : Controller {
float health;
@@ -26,9 +25,17 @@ struct Character : Controller {
DEATH = 1 << 9
};
Controller *viewTarget;
int jointChest;
int jointHead;
vec4 rangeChest;
vec4 rangeHead;
vec3 velocity;
float angleExt;
float speed;
int stepHeight;
int dropHeight;
int zone;
int box;
@@ -37,7 +44,13 @@ struct Character : Controller {
Collision collision;
Character(IGame *game, int entity, float health) : Controller(game, entity), health(health), tilt(0.0f), stand(STAND_GROUND), lastInput(0), velocity(0.0f), angleExt(0.0f) {
Character(IGame *game, int entity, float health) : Controller(game, entity), health(health), tilt(0.0f), stand(STAND_GROUND), lastInput(0), viewTarget(NULL), jointChest(-1), jointHead(-1), velocity(0.0f), angleExt(0.0f), speed(0.0f) {
stepHeight = 256;
dropHeight = -256;
rangeChest = vec4(-0.80f, 0.80f, -0.75f, 0.75f) * PI;
rangeHead = vec4(-0.25f, 0.25f, -0.50f, 0.50f) * PI;
animation.initOverrides();
rotHead = rotChest = quat(0, 0, 0, 1);
@@ -56,7 +69,7 @@ struct Character : Controller {
}
uint16* getZones() {
return flying ? level->zones[0].fly : level->zones[0].ground1;
return flying ? level->zones[level->isFlipped].fly : (stepHeight == 256 ? level->zones[level->isFlipped].ground1 : level->zones[level->isFlipped].ground2);
}
void rotateY(float delta) {
@@ -68,9 +81,9 @@ struct Character : Controller {
angle.x = clamp(angle.x + delta, -PI * 0.49f, PI * 0.49f);
}
virtual void hit(float damage, Controller *enemy = NULL) {
virtual void hit(float damage, Controller *enemy = NULL, TR::HitType hitType = TR::HIT_DEFAULT) {
health = max(0.0f, health - damage);
};
}
virtual void checkRoom() {
TR::Level::FloorInfo info;
@@ -95,10 +108,6 @@ struct Character : Controller {
}
}
virtual void cmdKill() {
health = 0;
}
virtual void updateVelocity() {}
virtual void updatePosition() {}
virtual Stand getStand() { return stand; }
@@ -185,15 +194,31 @@ struct Character : Controller {
stand = STAND_AIR;
}
virtual void doBubbles() {
int count = rand() % 3;
if (!count) return;
playSound(TR::SND_BUBBLE, pos, Sound::Flags::PAN);
vec3 head = animation.getJoints(getMatrix(), 14, true) * vec3(0.0f, 0.0f, 50.0f);
for (int i = 0; i < count; i++) {
int index = Sprite::add(game, TR::Entity::BUBBLE, getRoomIndex(), int(head.x), int(head.y), int(head.z), Sprite::FRAME_RANDOM, true);
if (index > -1)
level->entities[index].controller = new Bubble(game, index);
vec3 getViewPoint() {
return animation.getJoints(getMatrix(), jointChest).pos;
}
virtual void lookAt(Controller *target) {
if (health <= 0.0f)
target = NULL;
float speed = 8.0f * Core::deltaTime;
quat rot;
if (jointChest > -1) {
if (aim(target, jointChest, rangeChest, rot))
rotChest = rotChest.slerp(quat(0, 0, 0, 1).slerp(rot, 0.5f), speed);
else
rotChest = rotChest.slerp(quat(0, 0, 0, 1), speed);
animation.overrides[jointChest] = rotChest * animation.overrides[jointChest];
}
if (jointHead > -1) {
if (aim(target, jointHead, rangeHead, rot))
rotHead = rotHead.slerp(rot, speed);
else
rotHead = rotHead.slerp(quat(0, 0, 0, 1), speed);
animation.overrides[jointHead] = rotHead * animation.overrides[jointHead];
}
}
};

View File

@@ -41,17 +41,26 @@ struct Collision {
getFloor(level, roomIndex, vec3(pos.x, hpos.y, pos.z));
if (checkHeight(level, roomIndex, hpos, vec2(0.0f), height, 0xFFFFFF, 0xFFFFFF, side = NONE)) {
pos -= velocity;
pos.x -= velocity.x;
pos.z -= velocity.z;
side = FRONT;
return;
}
if (info[NONE].ceiling > hpos.y - maxHeight) {
pos.y = info[NONE].ceiling + maxHeight - offset.y;
side = TOP;
int hCell = info[NONE].ceiling - (int(hpos.y) - maxHeight);
if (hCell > 0) {
if (hCell > 128) {
pos.x -= velocity.x;
pos.z -= velocity.z;
side = FRONT;
} else {
pos.y = info[NONE].ceiling + maxHeight - offset.y;
side = TOP;
}
}
if (info[NONE].floor < hpos.y + minHeight) {
int hFloor = info[NONE].floor - (int(hpos.y) + minHeight);
if (hFloor < 0 && hFloor > -256) {
pos.y = info[NONE].floor - minHeight - offset.y;
side = BOTTOM;
}

View File

@@ -16,11 +16,24 @@
struct Controller;
struct ICamera {
vec4 *reflectPlane;
vec3 pos;
ICamera() : reflectPlane(NULL) {}
virtual void setup(bool calcMatrices) {}
virtual int getRoomIndex() const { return TR::NO_ROOM; }
};
struct IGame {
virtual ~IGame() {}
virtual void loadLevel(TR::LevelID id) {}
virtual TR::Level* getLevel() { return NULL; }
virtual MeshBuilder* getMesh() { return NULL; }
virtual Controller* getCamera() { return NULL; }
virtual ICamera* getCamera() { return NULL; }
virtual Controller* getLara() { return NULL; }
virtual bool isCutscene() { return false; }
virtual uint16 getRandomBox(uint16 zone, uint16 *zones) { return 0; }
virtual uint16 findPath(int ascend, int descend, bool big, int boxStart, int boxEnd, uint16 *zones, uint16 **boxes) { return 0; }
virtual void setClipParams(float clipSign, float clipHeight) {}
@@ -32,7 +45,7 @@ struct IGame {
virtual void renderEnvironment(int roomIndex, const vec3 &pos, Texture **targets, int stride = 0) {}
virtual void renderCompose(int roomIndex) {}
virtual void renderView(int roomIndex, bool water) {}
virtual void fxQuake(float time) {}
virtual void setEffect(TR::Effect effect, float param) {}
virtual bool invUse(TR::Entity::Type type) { return false; }
virtual void invAdd(TR::Entity::Type type, int count = 1) {}
@@ -40,9 +53,15 @@ struct IGame {
virtual bool invChooseKey(TR::Entity::Type hole) { return false; }
virtual Sound::Sample* playSound(int id, const vec3 &pos, int flags, int group = -1) const { return NULL; }
virtual void playTrack(int track, bool restart = false) {}
virtual void stopTrack() {}
};
struct Controller {
static Controller *first;
Controller *next;
enum ActiveState { asNone, asActive, asInactive } activeState;
IGame *game;
TR::Level *level;
int entity;
@@ -59,6 +78,8 @@ struct Controller {
vec3 ambient[6];
float specular;
float timer;
TR::Room::Light *targetLight;
vec3 mainLightPos;
vec4 mainLightColor;
@@ -68,18 +89,7 @@ struct Controller {
uint32 mask;
} *layers;
struct ActionCommand {
int emitter;
TR::Action action;
int value;
float timer;
ActionCommand *next;
ActionCommand() {}
ActionCommand(int emitter, TR::Action action, int value, float timer, ActionCommand *next = NULL) : emitter(emitter), action(action), value(value), timer(timer), next(next) {}
} *actionCommand;
Controller(IGame *game, int entity) : game(game), level(game->getLevel()), entity(entity), animation(level, getModel()), state(animation.state), layers(NULL), actionCommand(NULL) {
Controller(IGame *game, int entity) : next(NULL), activeState(asNone), game(game), level(game->getLevel()), entity(entity), animation(level, getModel()), state(animation.state), layers(NULL) {
TR::Entity &e = getEntity();
pos = vec3(float(e.x), float(e.y), float(e.z));
angle = vec3(0.0f, e.rotation, 0.0f);
@@ -87,14 +97,96 @@ struct Controller {
joints = m ? new Basis[m->mCount] : NULL;
frameIndex = -1;
specular = 0.0f;
timer = 0.0f;
ambient[0] = ambient[1] = ambient[2] = ambient[3] = ambient[4] = ambient[5] = vec3(intensityf(getRoom().ambient));
targetLight = NULL;
updateLights(false);
if (e.flags.once) {
e.flags.invisible = true;
e.flags.once = false;
}
if (e.flags.active == TR::ACTIVE) {
e.flags.active = 0;
e.flags.reverse = true;
activate();
}
if (e.isLara() || e.isActor()) // Lara and cutscene entities is active by default
activate();
}
virtual ~Controller() {
delete[] joints;
delete[] layers;
deactivate(true);
}
bool isActive() {
TR::Entity &e = getEntity();
if (e.flags.active != TR::ACTIVE)
return e.flags.reverse;
if (timer == 0.0f)
return !e.flags.reverse;
if (timer == -1.0f)
return e.flags.reverse;
timer = max(0.0f, timer - Core::deltaTime);
if (timer == 0.0f)
timer = -1.0f;
return !e.flags.reverse;
}
virtual bool activate() {
if (activeState != asNone)
return false;
getEntity().flags.invisible = false;
activeState = asActive;
next = first;
first = this;
return true;
}
virtual void deactivate(bool removeFromList = false) {
activeState = asInactive;
if (removeFromList) {
Controller *prev = NULL;
Controller *c = first;
while (c) {
if (c == this) {
if (prev)
prev->next = c->next;
else
first = c->next;
c->activeState = asNone;
break;
} else
prev = c;
c = c->next;
}
}
}
static void clearInactive() {
Controller *prev = NULL;
Controller *c = first;
while (c) {
if (c->activeState == asInactive) {
if (prev)
prev->next = c->next;
else
first = c->next;
c->activeState = asNone;
} else
prev = c;
c = c->next;
}
}
void initMeshOverrides() {
@@ -119,10 +211,9 @@ struct Controller {
layers[layer].mask = mask;
}
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();
bool aim(Controller *target, int joint, const vec4 &angleRange, quat &rot, quat *rotAbs = NULL) {
if (target) {
Box box = target->getBoundingBox();
vec3 t = (box.min + box.max) * 0.5f;
Basis b = animation.getJoints(Basis(getMatrix()), joint);
@@ -158,10 +249,10 @@ struct Controller {
e.rotation = angle.y;
}
bool insideRoom(const vec3 &pos, int room) const {
TR::Room &r = level->rooms[room];
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));
bool insideRoom(const vec3 &pos, int roomIndex) const {
TR::Room &r = level->rooms[roomIndex];
vec3 min = vec3((float)r.info.x + 1024, (float)r.info.yTop, (float)r.info.z + 1024);
vec3 max = min + vec3(float((r.xSectors - 1) * 1024), float(r.info.yBottom - r.info.yTop), float((r.zSectors - 1) * 1024));
return pos.x >= min.x && pos.x <= max.x &&
pos.y >= min.y && pos.y <= max.y &&
@@ -184,7 +275,10 @@ struct Controller {
}
virtual int getRoomIndex() const {
return getEntity().room;
int index = getEntity().room;
if (level->isFlipped && level->rooms[index].alternateRoom > -1)
index = level->rooms[index].alternateRoom;
return index;
}
virtual vec3& getPos() {
@@ -355,62 +449,13 @@ struct Controller {
return pos;
}
virtual void doBubbles() {
//
bool checkRange(Controller *target, float range) {
vec3 d = target->pos - pos;
return fabsf(d.x) < range && fabsf(d.z) < range && fabsf(d.y) < range;
}
void activateNext() { // activate next entity (for triggers)
if (!actionCommand || !actionCommand->next) {
actionCommand = NULL;
return;
}
ActionCommand *next = actionCommand->next;
virtual void hit(float damage, Controller *enemy = NULL, TR::HitType hitType = TR::HIT_DEFAULT) {}
Controller *controller = NULL;
switch (next->action) {
case TR::Action::ACTIVATE :
controller = (Controller*)level->entities[next->value].controller;
break;
case TR::Action::CAMERA_SWITCH :
case TR::Action::CAMERA_TARGET :
controller = (Controller*)level->cameraController;
break;
case TR::Action::SECRET :
if (!level->secrets[next->value]) {
level->secrets[next->value] = true;
playSound(TR::SND_SECRET, pos, 0);
}
actionCommand = next;
activateNext();
return;
case TR::Action::FLOW :
applyFlow(level->cameras[next->value]);
actionCommand = next;
activateNext();
break;
case TR::Action::FLIP_MAP :
case TR::Action::FLIP_ON :
case TR::Action::FLIP_OFF :
case TR::Action::END :
case TR::Action::SOUNDTRACK :
case TR::Action::HARDCODE :
case TR::Action::CLEAR :
case TR::Action::CAMERA_FLYBY :
case TR::Action::CUTSCENE :
//LOG("! action is not implemented\n");
actionCommand = next;
activateNext();
break;
}
if (controller) {
if (controller->activate(next))
actionCommand = NULL;
} else
actionCommand = NULL;
}
virtual bool activate(ActionCommand *cmd) { actionCommand = cmd; return true; }
virtual void doCustomCommand (int curFrame, int prevFrame) {}
virtual void checkRoom() {}
virtual void applyFlow(TR::Camera &sink) {}
@@ -422,7 +467,6 @@ struct Controller {
}
virtual void cmdJump(const vec3 &vel) {}
virtual void cmdKill() {}
virtual void cmdEmpty() {}
virtual void cmdEffect(int fx) { ASSERT(false); } // not implemented
@@ -441,7 +485,10 @@ struct Controller {
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_KILL :
if (animation.isEnded)
deactivate();
break;
case TR::ANIM_CMD_SOUND :
case TR::ANIM_CMD_EFFECT : {
int frame = (*ptr++) - anim->frameStart;
@@ -449,10 +496,10 @@ struct Controller {
if (animation.isFrameActive(frame)) {
if (cmd == TR::ANIM_CMD_EFFECT) {
switch (fx) {
case TR::EFFECT_ROTATE_180 : angle.y = angle.y + PI; break;
case TR::EFFECT_FLOOR_SHAKE : game->fxQuake(0.5f * max(0.0f, 1.0f - (pos - ((Controller*)level->cameraController)->pos).length2() / (15 * 1024 * 15 * 1024) )); break;
case TR::EFFECT_LARA_BUBBLES : doBubbles(); break;
default : cmdEffect(fx); break;
case TR::Effect::ROTATE_180 : angle.y = angle.y + PI; break;
case TR::Effect::FLOOR_SHAKE : game->setEffect(TR::Effect(fx), 0.5f * max(0.0f, 1.0f - (pos - ((ICamera*)level->cameraController)->pos).length2() / (15 * 1024 * 15 * 1024) )); break;
case TR::Effect::FLIP_MAP : level->isFlipped = !level->isFlipped; break;
default : cmdEffect(fx); break;
}
} else
playSound(fx, pos, Sound::Flags::PAN);
@@ -470,7 +517,6 @@ struct Controller {
if (animation.offset != 0.0f) cmdOffset(animation.offset);
if (animation.jump != 0.0f) cmdJump(animation.jump);
animation.playNext();
activateNext();
} else
animation.framePrev = animation.frameIndex;
}
@@ -524,7 +570,7 @@ struct Controller {
mat4 matrix;
TR::Entity &e = getEntity();
if (e.type < TR::Entity::CUT_1 || e.type > TR::Entity::CUT_4) { // TODO: move to ctor
if (!e.isActor()) {
matrix.identity();
matrix.translate(pos);
if (angle.y != 0.0f) matrix.rotateY(angle.y - (animation.flip ? PI * animation.delta : 0.0f));
@@ -539,7 +585,10 @@ struct Controller {
void renderShadow(MeshBuilder *mesh) {
TR::Entity &entity = getEntity();
if (Core::pass != Core::passCompose || !TR::castShadow(entity.type))
if (Core::pass != Core::passCompose || !entity.castShadow())
return;
if (entity.isActor()) // cutscene entities have no blob shadow
return;
Box box = animation.getBoundingBox(pos, 0);
@@ -570,8 +619,6 @@ struct Controller {
Core::setBlending(bmMultiply);
mesh->renderShadowBlob();
Core::setBlending(bmNone);
Core::active.shader->setParam(uViewProj, Core::mViewProj);
}
virtual void render(Frustum *frustum, MeshBuilder *mesh, Shader::Type type, bool caustics) { // TODO: animation.calcJoints
@@ -616,4 +663,6 @@ struct Controller {
}
};
Controller *Controller::first = NULL;
#endif

View File

@@ -15,20 +15,16 @@
#define GL_CLAMP_TO_BORDER 0x812D
#define GL_TEXTURE_BORDER_COLOR 0x1004
#define GL_TEXTURE_COMPARE_MODE 0x884C
#define GL_TEXTURE_COMPARE_FUNC 0x884D
#define GL_COMPARE_REF_TO_TEXTURE 0x884E
#define GL_TEXTURE_COMPARE_MODE 0x884C
#define GL_TEXTURE_COMPARE_FUNC 0x884D
#define GL_COMPARE_REF_TO_TEXTURE 0x884E
#undef GL_RGBA32F
#undef GL_RGBA16F
#undef GL_HALF_FLOAT
#define GL_RGBA16F 0x881A
#define GL_RGBA32F 0x8814
#define GL_HALF_FLOAT 0x140B
#define GL_RGBA32F GL_RGBA
#define GL_RGBA16F GL_RGBA
#define GL_HALF_FLOAT GL_HALF_FLOAT_OES
#define GL_DEPTH_STENCIL GL_DEPTH_STENCIL_OES
#define GL_UNSIGNED_INT_24_8 GL_UNSIGNED_INT_24_8_OES
#define GL_DEPTH_STENCIL GL_DEPTH_STENCIL_OES
#define GL_UNSIGNED_INT_24_8 GL_UNSIGNED_INT_24_8_OES
#define PFNGLGENVERTEXARRAYSPROC PFNGLGENVERTEXARRAYSOESPROC
#define PFNGLDELETEVERTEXARRAYSPROC PFNGLDELETEVERTEXARRAYSOESPROC
@@ -43,6 +39,38 @@
#define glProgramBinary glProgramBinaryOES
#define GL_PROGRAM_BINARY_LENGTH GL_PROGRAM_BINARY_LENGTH_OES
#elif __RPI__
#define MOBILE
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#define GL_CLAMP_TO_BORDER 0x812D
#define GL_TEXTURE_BORDER_COLOR 0x1004
#define GL_TEXTURE_COMPARE_MODE 0x884C
#define GL_TEXTURE_COMPARE_FUNC 0x884D
#define GL_COMPARE_REF_TO_TEXTURE 0x884E
#undef GL_RGBA32F
#undef GL_RGBA16F
#undef GL_HALF_FLOAT
#define GL_RGBA32F GL_RGBA
#define GL_RGBA16F GL_RGBA
#define GL_HALF_FLOAT GL_HALF_FLOAT_OES
#define GL_DEPTH_STENCIL GL_DEPTH_STENCIL_OES
#define GL_UNSIGNED_INT_24_8 GL_UNSIGNED_INT_24_8_OES
#define glGenVertexArrays(...)
#define glDeleteVertexArrays(...)
#define glBindVertexArray(...)
#define GL_PROGRAM_BINARY_LENGTH GL_PROGRAM_BINARY_LENGTH_OES
#define glGetProgramBinary(...)
#define glProgramBinary(...)
#elif __linux__
#define LINUX 1
#include <GL/gl.h>
@@ -64,12 +92,12 @@
#define glDeleteVertexArrays glDeleteVertexArraysOES
#define glBindVertexArray glBindVertexArrayOES
#define GL_CLAMP_TO_BORDER GL_CLAMP_TO_BORDER_EXT
#define GL_TEXTURE_BORDER_COLOR GL_TEXTURE_BORDER_COLOR_EXT
#define GL_CLAMP_TO_BORDER 0x812D
#define GL_TEXTURE_BORDER_COLOR 0x1004
#define GL_TEXTURE_COMPARE_MODE GL_TEXTURE_COMPARE_MODE_EXT
#define GL_TEXTURE_COMPARE_FUNC GL_TEXTURE_COMPARE_FUNC_EXT
#define GL_COMPARE_REF_TO_TEXTURE GL_COMPARE_REF_TO_TEXTURE_EXT
#define GL_TEXTURE_COMPARE_MODE GL_TEXTURE_COMPARE_MODE_EXT
#define GL_TEXTURE_COMPARE_FUNC GL_TEXTURE_COMPARE_FUNC_EXT
#define GL_COMPARE_REF_TO_TEXTURE GL_COMPARE_REF_TO_TEXTURE_EXT
#else
#include <Carbon/Carbon.h>
#include <AudioToolbox/AudioQueue.h>
@@ -78,13 +106,14 @@
#include <OpenGL/glext.h>
#include <AGL/agl.h>
#define GL_RGBA32F GL_RGBA
#define GL_RGBA16F GL_RGBA
#define GL_RGBA16F 0x881A
#define GL_RGBA32F 0x8814
#define GL_HALF_FLOAT 0x140B
#define GL_RGB565 GL_RGBA
#define GL_TEXTURE_COMPARE_MODE 0x884C
#define GL_TEXTURE_COMPARE_FUNC 0x884D
#define GL_COMPARE_REF_TO_TEXTURE 0x884E
#define GL_TEXTURE_COMPARE_MODE 0x884C
#define GL_TEXTURE_COMPARE_FUNC 0x884D
#define GL_COMPARE_REF_TO_TEXTURE 0x884E
#define glGenVertexArrays glGenVertexArraysAPPLE
#define glDeleteVertexArrays glDeleteVertexArraysAPPLE
@@ -96,18 +125,10 @@
#endif
#elif __EMSCRIPTEN__
#define MOBILE
#include <emscripten.h>
#include <html5.h>
#include <emscripten/emscripten.h>
#include <emscripten/html5.h>
#include <GLES3/gl3.h>
#include <GLES3/gl2ext.h>
#undef GL_RGBA32F
#undef GL_RGBA16F
#undef GL_HALF_FLOAT
#define GL_RGBA32F GL_RGBA
#define GL_RGBA16F GL_RGBA
#define GL_HALF_FLOAT GL_HALF_FLOAT_OES
#define GL_CLAMP_TO_BORDER GL_CLAMP_TO_BORDER_EXT
#define GL_TEXTURE_BORDER_COLOR GL_TEXTURE_BORDER_COLOR_EXT
@@ -119,13 +140,32 @@
namespace Core {
float deltaTime;
int width, height;
struct {
struct {
bool ambient;
bool lighting;
bool shadows;
bool water;
bool contact;
} detail;
struct {
bool retarget;
} controls;
struct {
bool reverb;
} audio;
} settings;
}
#include "utils.h"
#include "input.h"
#include "sound.h"
#if defined(WIN32) || defined(LINUX) || defined(ANDROID)
#if defined(WIN32) || (defined(LINUX) && !defined(__RPI__)) || defined(ANDROID)
#ifdef ANDROID
#define GetProc(x) dlsym(libGL, x);
@@ -133,6 +173,8 @@ namespace Core {
void* GetProc(const char *name) {
#ifdef WIN32
return (void*)wglGetProcAddress(name);
#elif __RPI__
return (void*)eglGetProcAddress(name);
#elif LINUX
return (void*)glXGetProcAddress((GLubyte*)name);
#endif
@@ -147,6 +189,7 @@ namespace Core {
#endif
#if defined(WIN32) || defined(LINUX)
PFNGLGENERATEMIPMAPPROC glGenerateMipmap;
// Profiling
#ifdef PROFILE
PFNGLOBJECTLABELPROC glObjectLabel;
@@ -210,10 +253,12 @@ namespace Core {
PFNGLDISCARDFRAMEBUFFEREXTPROC glDiscardFramebufferEXT;
#endif
#define MAX_LIGHTS 4
#define MAX_CACHED_LIGHTS 3
#define MAX_RENDER_BUFFERS 32
#define MAX_CONTACTS 15
#define MAX_LIGHTS 4
#define MAX_CACHED_LIGHTS 3
#define MAX_RENDER_BUFFERS 32
#define MAX_CONTACTS 15
#define MAX_ANIM_TEX_RANGES 16
#define MAX_ANIM_TEX_OFFSETS 32
struct Shader;
struct Texture;
@@ -242,7 +287,7 @@ typedef unsigned short Index;
struct Vertex {
short4 coord; // xyz - position, w - joint index (for entities only)
short4 normal; // xyz - vertex normal<EFBFBD> w - unused
short4 normal; // xyz - vertex normal, w - unused
short4 texCoord; // xy - texture coordinates, zw - trapezoid warping
ubyte4 param; // xy - anim tex range and frame index, zw - unused
ubyte4 color; // xyz - color, w - intensity
@@ -258,8 +303,9 @@ namespace Core {
bool texNPOT;
bool texRG;
bool texBorder;
bool texFloat, texFloatLinear;
bool texHalf, texHalfLinear;
int8 texAniso;
bool colorFloat, texFloat, texFloatLinear;
bool colorHalf, texHalf, texHalfLinear;
#ifdef PROFILE
bool profMarker;
bool profTiming;
@@ -268,13 +314,41 @@ namespace Core {
}
#ifdef PROFILE
#define USE_CV_MARKERS
#ifdef USE_CV_MARKERS
#include <libs/cvmarkers/cvmarkersobj.h>
using namespace Concurrency::diagnostic;
marker_series *series[256];
int seriesIndex;
#endif
struct Marker {
#ifdef USE_CV_MARKERS
span *cvSpan;
#endif
Marker(const char *title) {
if (Core::support.profMarker) glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 1, -1, title);
#ifdef USE_CV_MARKERS
marker_series *&s = series[seriesIndex];
if (s == NULL) {
char seriesTitle[64];
sprintf(seriesTitle, "events - %d", seriesIndex);
s = new marker_series(seriesTitle);
}
cvSpan = new span(*s, normal_importance, _T(title));
seriesIndex++;
#endif
}
~Marker() {
if (Core::support.profMarker) glPopDebugGroup();
#ifdef USE_CV_MARKERS
delete cvSpan;
seriesIndex--;
#endif
}
static void setLabel(GLenum id, GLuint name, const char *label) {
@@ -386,20 +460,6 @@ namespace Core {
frame++;
}
} stats;
struct {
struct {
bool ambient;
bool lighting;
bool shadows;
bool water;
bool contact;
} detail;
struct {
bool retarget;
} controls;
} settings;
}
#include "texture.h"
@@ -417,12 +477,14 @@ namespace Core {
void *libGL = dlopen("libGLESv2.so", RTLD_LAZY);
#endif
#if defined(WIN32) || defined(LINUX) || defined(ANDROID)
#if defined(WIN32) || (defined(LINUX) && !defined(__RPI__)) || defined(ANDROID)
#ifdef WIN32
GetProcOGL(glActiveTexture);
#endif
#if defined(WIN32) || defined(LINUX)
GetProcOGL(glGenerateMipmap);
#ifdef PROFILE
GetProcOGL(glObjectLabel);
GetProcOGL(glPushDebugGroup);
@@ -485,9 +547,22 @@ namespace Core {
GetProcOGL(glProgramBinary);
#endif
char *ext = (char*)glGetString(GL_EXTENSIONS);
//LOG("%s\n", ext);
char *ext = (char*)glGetString(GL_EXTENSIONS);
/*
if (ext != NULL) {
char buf[255];
int len = strlen(ext);
int start = 0;
for (int i = 0; i < len; i++)
if (ext[i] == ' ' || (i == len - 1)) {
memcpy(buf, &ext[start], i - start);
buf[i - start] = 0;
LOG("%s\n", buf);
start = i + 1;
}
}
*/
support.shaderBinary = extSupport(ext, "_program_binary");
support.VAO = extSupport(ext, "_vertex_array_object");
support.depthTexture = extSupport(ext, "_depth_texture");
@@ -496,16 +571,24 @@ namespace Core {
support.texNPOT = extSupport(ext, "_texture_npot") || extSupport(ext, "_texture_non_power_of_two");
support.texRG = extSupport(ext, "_texture_rg "); // hope that isn't last extension in string ;)
support.texBorder = extSupport(ext, "_texture_border_clamp");
support.texFloatLinear = extSupport(ext, "GL_ARB_texture_float") || extSupport(ext, "_texture_float_linear");
support.texAniso = extSupport(ext, "_texture_filter_anisotropic");
support.colorFloat = extSupport(ext, "_color_buffer_float");
support.colorHalf = extSupport(ext, "_color_buffer_half_float") || extSupport(ext, "GL_ARB_half_float_pixel");
support.texFloatLinear = support.colorFloat || extSupport(ext, "GL_ARB_texture_float") || extSupport(ext, "_texture_float_linear");
support.texFloat = support.texFloatLinear || extSupport(ext, "_texture_float");
support.texHalfLinear = extSupport(ext, "GL_ARB_texture_float") || extSupport(ext, "_texture_half_float_linear");
support.texHalfLinear = support.colorHalf || extSupport(ext, "GL_ARB_texture_float") || extSupport(ext, "_texture_half_float_linear") || extSupport(ext, "_color_buffer_half_float");
support.texHalf = support.texHalfLinear || extSupport(ext, "_texture_half_float");
if (support.texAniso) {
int maxAniso;
glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAniso);
support.texAniso = maxAniso;
}
#ifdef PROFILE
support.profMarker = extSupport(ext, "_KHR_debug");
support.profTiming = extSupport(ext, "_timer_query");
#endif
char *vendor = (char*)glGetString(GL_VENDOR);
LOG("Vendor : %s\n", vendor);
LOG("Renderer : %s\n", glGetString(GL_RENDERER));
@@ -520,13 +603,15 @@ namespace Core {
LOG(" NPOT textures : %s\n", support.texNPOT ? "true" : "false");
LOG(" RG textures : %s\n", support.texRG ? "true" : "false");
LOG(" border color : %s\n", support.texBorder ? "true" : "false");
LOG(" anisotropic : %d\n", support.texAniso);
LOG(" float textures : float = %s, half = %s\n",
support.texFloat ? (support.texFloatLinear ? "linear" : "nearest") : "false",
support.texHalf ? (support.texHalfLinear ? "linear" : "nearest") : "false");
support.colorFloat ? "full" : (support.texFloat ? (support.texFloatLinear ? "linear" : "nearest") : "false"),
support.colorHalf ? "full" : (support.texHalf ? (support.texHalfLinear ? "linear" : "nearest") : "false"));
LOG("\n");
glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&defaultFBO);
glGenFramebuffers(1, &FBO);
memset(rtCache, 0, sizeof(rtCache));
defaultTarget = NULL;
@@ -541,7 +626,7 @@ namespace Core {
uint32 data = 0x00000000;
blackTex = new Texture(1, 1, Texture::RGBA, false, &data, false);
data = 0xFFFFFFFF;
whiteTex = new Texture(1, 1, Texture::RGBA, false, &data, false);
whiteTex = new Texture(1, 1, Texture::RGBA, false, &data, false);
}
void free() {
@@ -752,7 +837,7 @@ namespace Core {
void copyTarget(Texture *dst, int xOffset, int yOffset, int x, int y, int width, int height) {
validateRenderState();
dst->bind(sDiffuse);
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, x, y, width, height);
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, x, y, width, height); // TODO: too bad for iOS devices!
}
vec4 copyPixel(int x, int y) { // GPU sync!
@@ -776,6 +861,12 @@ namespace Core {
}
void endFrame() {
#ifdef __EMSCRIPTEN__
glColorMask(false, false, false, true);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glColorMask(true, true, true, true);
#endif
Core::stats.stop();
}

View File

@@ -382,7 +382,6 @@ namespace Debug {
glBegin(GL_QUADS);
for (int i = 0; i < level.roomsCount; i++) {
// if (level.entities[91].room != i) continue;
TR::Room &r = level.rooms[i];
for (int j = 0; j < r.portalsCount; j++) {
TR::Room::Portal &p = r.portals[j];
@@ -453,8 +452,6 @@ namespace Debug {
}
void lights(const TR::Level &level, int room, Controller *lara) {
// int roomIndex = level.entities[lara->entity].room;
// int lightIndex = getLightIndex(lara->pos, roomIndex);
glPointSize(8);
for (int i = 0; i < level.roomsCount; i++)
for (int j = 0; j < level.rooms[i].lightsCount; j++) {
@@ -560,7 +557,7 @@ namespace Debug {
sprintf(buf, "samples_PSX/%03d.wav", index);
FILE *f = fopen(buf, "wb");
if (level->version == TR::Level::VER_TR1_PSX) {
if (level->version == TR::VER_TR1_PSX) {
int dataSize = level->soundSize[index] / 16 * 28 * 2 * 4;
struct Header {
@@ -595,7 +592,7 @@ namespace Debug {
fwrite(&frames[i].L, 2, 1, f);
}
if (level->version == TR::Level::VER_TR1_PC) {
if (level->version == TR::VER_TR1_PC) {
uint32 *data = (uint32*)&level->soundData[level->soundOffsets[index]];
fwrite(data, data[1] + 8, 1, f);
}
@@ -616,7 +613,6 @@ namespace Debug {
case_name(TR::Level::Trigger, ANTIPAD );
case_name(TR::Level::Trigger, COMBAT );
case_name(TR::Level::Trigger, DUMMY );
case_name(TR::Level::Trigger, ANTI );
}
return "UNKNOWN";
}
@@ -626,17 +622,14 @@ namespace Debug {
case_name(TR::Action, ACTIVATE );
case_name(TR::Action, CAMERA_SWITCH );
case_name(TR::Action, FLOW );
case_name(TR::Action, FLIP_MAP );
case_name(TR::Action, FLIP );
case_name(TR::Action, FLIP_ON );
case_name(TR::Action, FLIP_OFF );
case_name(TR::Action, CAMERA_TARGET );
case_name(TR::Action, END );
case_name(TR::Action, SOUNDTRACK );
case_name(TR::Action, HARDCODE );
case_name(TR::Action, EFFECT );
case_name(TR::Action, SECRET );
case_name(TR::Action, CLEAR );
case_name(TR::Action, CAMERA_FLYBY );
case_name(TR::Action, CUTSCENE );
}
return "UNKNOWN";
}
@@ -654,24 +647,31 @@ namespace Debug {
void info(const TR::Level &level, const TR::Entity &entity, Animation &anim) {
float y = 0.0f;
int activeCount = 0;
Controller *c = Controller::first;
while (c) {
activeCount++;
c = c->next;
}
char buf[255];
sprintf(buf, "DIP = %d, TRI = %d, SND = %d", Core::stats.dips, Core::stats.tris, Sound::channelsCount);
sprintf(buf, "DIP = %d, TRI = %d, SND = %d, active = %d", Core::stats.dips, Core::stats.tris, Sound::channelsCount, activeCount);
Debug::Draw::text(vec2(16, y += 16), vec4(1.0f), buf);
vec3 angle = ((Controller*)entity.controller)->angle * RAD2DEG;
sprintf(buf, "pos = (%d, %d, %d), angle = (%d, %d), room = %d", entity.x, entity.y, entity.z, (int)angle.x, (int)angle.y, entity.room);
sprintf(buf, "pos = (%d, %d, %d), angle = (%d, %d), room = %d (camera: %d)", entity.x, entity.y, entity.z, (int)angle.x, (int)angle.y, ((Controller*)entity.controller)->getRoomIndex(), ((ICamera*)level.cameraController)->getRoomIndex());
Debug::Draw::text(vec2(16, y += 16), 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, y += 16), vec4(1.0f), buf);
TR::Level::FloorInfo info;
level.getFloorInfo(entity.room, entity.x, entity.y, entity.z, info);
level.getFloorInfo(((Controller*)entity.controller)->getRoomIndex(), entity.x, entity.y, 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, y += 16), vec4(1.0f), buf);
if (info.trigCmdCount > 0) {
y += 16;
sprintf(buf, "trigger: %s%s mask: %d", getTriggerType(level, info.trigger), info.trigInfo.once ? " (once)" : "", info.trigInfo.mask);
sprintf(buf, "trigger: %s%s mask: %d timer: %d", getTriggerType(level, info.trigger), info.trigInfo.once ? " (once)" : "", info.trigInfo.mask, info.trigInfo.timer);
Debug::Draw::text(vec2(16, y += 16), vec4(0.5f, 0.8f, 0.5f, 1.0f), buf);
for (int i = 0; i < info.trigCmdCount; i++) {
@@ -679,6 +679,11 @@ namespace Debug {
const char *ent = (cmd.action == TR::Action::ACTIVATE || cmd.action == TR::Action::CAMERA_TARGET) ? getEntityName(level, level.entities[cmd.args]) : "";
sprintf(buf, "%s -> %s (%d)", getTriggerAction(level, cmd.action), ent, cmd.args);
if (cmd.action == TR::Action::CAMERA_SWITCH) {
i++;
sprintf(buf, "%s delay: %d speed: %d", buf, int(info.trigCmd[i].timer), int(info.trigCmd[i].speed) * 8 + 1);
}
Debug::Draw::text(vec2(16, y += 16), vec4(0.1f, 0.6f, 0.1f, 1.0f), buf);
}
}

View File

@@ -57,15 +57,10 @@ struct Enemy : Character {
float length; // dist from center to head (jaws)
float aggression;
int radius;
int stepHeight;
int dropHeight;
Character *target;
Path *path;
int jointChest;
int jointHead;
float targetDist;
bool targetDead;
bool targetInView; // target in enemy view zone
@@ -73,11 +68,6 @@ struct Enemy : Character {
bool targetCanAttack;
Enemy(IGame *game, int entity, float health, int radius, float length, float aggression) : Character(game, entity, health), ai(AI_RANDOM), mood(MOOD_SLEEP), wound(false), nextState(0), targetBox(-1), thinkTime(1.0f / 30.0f), length(length), aggression(aggression), radius(radius), target(NULL), path(NULL) {
stepHeight = 256;
dropHeight = -256;
jointChest = jointHead = -1;
targetDist = +INF;
targetInView = targetFromView = targetCanAttack = false;
}
@@ -86,24 +76,12 @@ struct Enemy : Character {
delete path;
}
virtual bool activate(ActionCommand *cmd) {
#ifdef LEVEL_EDITOR
return true;
#endif
Controller::activate(cmd);
getEntity().flags.active = true;
activateNext();
for (int i = 0; i < level->entitiesCount; i++)
if (level->entities[i].type == TR::Entity::LARA) {
target = (Character*)level->entities[i].controller;
break;
}
ASSERT(target);
return true;
virtual bool activate() {
if (Character::activate()) {
target = (Character*)game->getLara();
return true;
}
return false;
}
virtual void updateVelocity() {
@@ -194,27 +172,6 @@ struct Enemy : Character {
animation.overrideMask &= ~(1 << chest);
}
void lookAt(int target, int chest, int head, bool rotate = false) {
float speed = 8.0f * Core::deltaTime;
quat rot;
if (chest > -1) {
if (rotate && 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);
animation.overrides[chest] = rotChest * animation.overrides[chest];
}
if (head > -1) {
if (rotate && aim(target, head, vec4(-PI * 0.25f, PI * 0.25f, -PI * 0.5f, PI * 0.5f), rot))
rotHead = rotHead.slerp(rot, speed);
else
rotHead = rotHead.slerp(quat(0, 0, 0, 1), speed);
animation.overrides[head] = rotHead * animation.overrides[head];
}
}
bool getTargetInfo(int height, vec3 *pos, float *angleX, float *angleY, float *dist) {
vec3 p = waypoint;
p.y -= height;
@@ -232,6 +189,10 @@ struct Enemy : Character {
return true;
}
virtual void lookAt(Controller *target) {
Character::lookAt(targetInView ? target : NULL);
}
int turn(float delta, float speed) {
float w = speed * Core::deltaTime;
@@ -257,8 +218,8 @@ struct Enemy : Character {
return 0;
}
virtual void hit(float damage, Controller *enemy = NULL) {
Character::hit(damage, enemy);
virtual void hit(float damage, Controller *enemy = NULL, TR::HitType hitType = TR::HIT_DEFAULT) {
Character::hit(damage, enemy, hitType);
wound = true;
};
@@ -638,7 +599,7 @@ struct Wolf : Enemy {
Enemy::updatePosition();
setOverrides(state != STATE_DEATH, jointChest, jointHead);
lookAt(target ? target->entity : -1, jointChest, jointHead);
lookAt(target);
}
};
@@ -787,7 +748,7 @@ struct Bear : Enemy {
Enemy::updatePosition();
setOverrides(state == STATE_RUN || state == STATE_WALK || state == STATE_HIND, jointChest, jointHead);
lookAt(target ? target->entity : -1, jointChest, jointHead);
lookAt(target);
}
};
@@ -875,6 +836,7 @@ struct Bat : Enemy {
#define REX_DIST_WALK 5120
#define REX_TURN_FAST (DEG2RAD * 120)
#define REX_TURN_SLOW (DEG2RAD * 60)
#define REX_DAMAGE 1000
struct Rex : Enemy {
@@ -950,7 +912,7 @@ struct Rex : Enemy {
break;
case STATE_BITE :
if (mask & HIT_MASK) {
target->hit(10000, this);
target->hit(REX_DAMAGE, this, TR::HIT_REX);
return STATE_FATAL;
}
nextState = STATE_WALK;
@@ -979,7 +941,7 @@ struct Rex : Enemy {
Enemy::updatePosition();
setOverrides(true, jointChest, jointHead);
lookAt(target ? target->entity : -1, jointChest, jointHead, targetInView && state != STATE_DEATH && state != STATE_FATAL);
lookAt(target);
}
};
@@ -1097,7 +1059,7 @@ struct Raptor : Enemy {
Enemy::updatePosition();
setOverrides(true, jointChest, jointHead);
lookAt(target ? target->entity : -1, jointChest, jointHead, targetInView && state != STATE_DEATH);
lookAt(target);
}
};

View File

@@ -4,7 +4,9 @@
#include "utils.h"
#define MAX_RESERVED_ENTITIES 128
#define MAX_FLIPMAP_COUNT 16
#define MAX_SECRETS_COUNT 16
#define MAX_TRACKS_COUNT 64
#define MAX_TRIGGER_COMMANDS 32
#define MAX_MESHES 512
@@ -52,7 +54,7 @@
E( TRAP_DARTGUN ) \
E( DOOR_LIFT ) \
E( TRAP_SLAM ) \
E( FALLING_SWORD ) \
E( TRAP_SWORD ) \
E( HAMMER_HANDLE ) \
E( HAMMER_BLOCK ) \
E( LIGHTNING_BALL ) \
@@ -62,8 +64,8 @@
E( BLOCK_3 ) \
E( BLOCK_4 ) \
E( MOVING_BLOCK ) \
E( FALLING_CEILING_1 ) \
E( FALLING_CEILING_2 ) \
E( TRAP_CEILING_1 ) \
E( TRAP_CEILING_2 ) \
E( SWITCH ) \
E( SWITCH_WATER ) \
E( DOOR_1 ) \
@@ -138,10 +140,10 @@
E( LEADBAR ) \
E( INV_LEADBAR ) \
E( MIDAS_TOUCH ) \
E( KEY_1 ) \
E( KEY_2 ) \
E( KEY_3 ) \
E( KEY_4 ) \
E( KEY_ITEM_1 ) \
E( KEY_ITEM_2 ) \
E( KEY_ITEM_3 ) \
E( KEY_ITEM_4 ) \
E( INV_KEY_1 ) \
E( INV_KEY_2 ) \
E( INV_KEY_3 ) \
@@ -204,8 +206,10 @@
namespace TR {
enum {
FLOOR_BLOCK = -127,
NO_ROOM = 0xFF,
NO_FLOOR = -127,
NO_ROOM = 0xFF,
NO_BOX = 0xFFFF,
ACTIVE = 0x1F,
};
enum {
@@ -218,56 +222,26 @@ namespace TR {
ANIM_CMD_EFFECT ,
};
// https://dl.dropboxusercontent.com/u/62482708/Secret/TR4%26TR5%20PSX%20Stuff.zip
enum {
EFFECT_ROTATE_180 ,
EFFECT_FLOOR_SHAKE ,
EFFECT_LARA_NORMAL ,
EFFECT_LARA_BUBBLES ,
EFFECT_FINISH_LEVEL ,
EFFECT_ACTIVATE_CAMERA ,
EFFECT_ACTIVATE_KEY ,
EFFECT_RUBBLEFX ,
EFFECT_CROWBAR ,
EFFECT_CURTAINFX ,
EFFECT_SETCHANGEFX ,
EFFECT_EXPLOSION_FX ,
EFFECT_LARA_HANDSFREE ,
EFFECT_FLIP_MAP ,
EFFECT_DRAW_RIGHTGUN ,
EFFECT_DRAW_LEFTGUN ,
EFFECT_SHOOT_RIGHTGUN ,
EFFECT_SHOOT_LEFTGUN ,
EFFECT_MESH_SWAP1 ,
EFFECT_MESH_SWAP2 ,
EFFECT_MESH_SWAP3 ,
EFFECT_INV_ON ,
EFFECT_INV_OFF ,
EFFECT_DYN_ON ,
EFFECT_DYN_OFF ,
EFFECT_STATUEFX ,
EFFECT_RESET_HAIR ,
EFFECT_BOILERFX ,
EFFECT_SETFOG ,
EFFECT_GHOSTTRAP ,
EFFECT_LARALOCATION ,
EFFECT_CLEARSCARABS ,
EFFECT_FOOTPRINT_FX ,
EFFECT_FLIP_MAP0 ,
EFFECT_FLIP_MAP1 ,
EFFECT_FLIP_MAP2 ,
EFFECT_FLIP_MAP3 ,
EFFECT_FLIP_MAP4 ,
EFFECT_FLIP_MAP5 ,
EFFECT_FLIP_MAP6 ,
EFFECT_FLIP_MAP7 ,
EFFECT_FLIP_MAP8 ,
EFFECT_FLIP_MAP9 ,
EFFECT_POURSWAP1 ,
EFFECT_POURSWAP2 ,
EFFECT_LARALOCATIONPAD ,
EFFECT_KILLACTIVEBADDIES,
};
enum Effect : int32 {
NONE = -1,
ROTATE_180 ,
FLOOR_SHAKE ,
LARA_NORMAL ,
LARA_BUBBLES ,
FINISH_LEVEL ,
EARTHQUAKE ,
FLOOD ,
UNK1 ,
UNK2 ,
UNK3 ,
UNK4 ,
EXPLOSION ,
LARA_HANDSFREE ,
FLIP_MAP ,
DRAW_RIGHTGUN ,
UNK5 ,
FLICKER ,
};
enum {
SND_NO = 2,
@@ -290,6 +264,8 @@ namespace TR {
SND_SHOTGUN_SHOT = 45,
SND_UNDERWATER = 60,
SND_FLOOD = 81,
SND_INV_SPIN = 108,
SND_INV_HOME = 109,
@@ -306,6 +282,19 @@ namespace TR {
SND_SECRET = 173,
};
enum {
TRACK_TITLE = 2,
TRACK_CAVES = 5,
TRACK_SECRET = 13,
TRACK_CISTERN = 57,
TRACK_EGYPT = 58,
TRACK_MINE = 59,
TRACK_CUT1 = 23,
TRACK_CUT2 = 25,
TRACK_CUT3 = 24,
TRACK_CUT4 = 22,
};
enum {
MODEL_LARA = 0,
MODEL_PISTOLS = 1,
@@ -315,21 +304,26 @@ namespace TR {
MODEL_LARA_SPEC = 5,
};
enum HitType {
HIT_DEFAULT,
HIT_BLADE,
HIT_BOULDER,
HIT_SPIKES,
HIT_REX,
};
enum Action : uint16 {
ACTIVATE , // activate item
CAMERA_SWITCH , // switch to camera
FLOW , // underwater flow
FLIP_MAP , // flip map
FLIP , // flip map
FLIP_ON , // flip on
FLIP_OFF , // flip off
CAMERA_TARGET , // look at item
END , // end level
SOUNDTRACK , // play soundtrack
HARDCODE , // special hadrdcode trigger
EFFECT , // special effect trigger
SECRET , // secret found
CLEAR , // clear bodies
CAMERA_FLYBY , // flyby camera sequence
CUTSCENE , // play cutscene
};
namespace Limits {
@@ -337,39 +331,43 @@ namespace TR {
struct Limit {
float dy, dz, ay;
::Box box;
bool alignAngle;
bool alignHoriz;
};
Limit SWITCH = {
0, 376, 30, {{-200, 0, 312}, {200, 0, 512}}
0, 376, 30, {{-200, 0, 312}, {200, 0, 512}}, true, false
};
Limit SWITCH_UNDERWATER = {
0, 100, 80, {{-1024, -1024, -1024}, {1024, 1024, 512}}
0, 100, 80, {{-1024, -1024, -1024}, {1024, 1024, 512}}, true, true
};
Limit PICKUP = {
0, -100, 180, {{-256, -100, -256}, {256, 100, 100}}
0, -100, 180, {{-256, -100, -256}, {256, 100, 100}}, false, true
};
Limit PICKUP_UNDERWATER = {
-200, -350, 45, {{-512, -512, -512}, {512, 512, 512}}
-200, -350, 45, {{-512, -512, -512}, {512, 512, 512}}, false, true
};
Limit KEY_HOLE = {
0, 362, 30, {{-200, 0, 312}, {200, 0, 512}}
0, 362, 30, {{-200, 0, 312}, {200, 0, 512}}, true, true
};
Limit PUZZLE_HOLE = {
0, 327, 30, {{-200, 0, 312}, {200, 0, 512}}
0, 327, 30, {{-200, 0, 312}, {200, 0, 512}}, true, true
};
Limit BLOCK = {
0, -612, 30, {{-300, 0, -692}, {300, 0, -512}}
0, -612, 30, {{-300, 0, -692}, {300, 0, -512}}, true, false
};
Limit SCION = {
640, -202, 30, {{-256, 540, -350}, {256, 740, -200}}, false, false
};
}
#pragma pack(push, 1)
struct fixed {
uint16 L;
int16 H;
@@ -418,12 +416,12 @@ namespace TR {
struct Rectangle {
uint16 vertices[4];
uint16 texture;
uint16 texture:15, color:1;
};
struct Triangle {
uint16 vertices[3];
uint16 texture;
uint16 texture:15, color:1;
};
struct Tile4 {
@@ -437,7 +435,7 @@ namespace TR {
};
struct Tile32 {
Color32 color[256 * 256];
Color32 color[256 * 256]; // + 128 for mips data
};
struct CLUT {
@@ -550,7 +548,7 @@ namespace TR {
uint16 end:1;
};
struct {
uint16 delay:8, once:1, timer:7;
uint16 timer:8, once:1, speed:5, :2;
};
} triggerCmd;
@@ -568,10 +566,9 @@ namespace TR {
uint16 boxIndex:15, end:1;
};
//struct Collider {
// uint16 radius:10, info:6;
// uint16 flags:16;
//};
struct Flags {
uint16 :8, once:1, active:5, :2;
};
// internal mesh structure
struct Mesh {
@@ -614,42 +611,54 @@ namespace TR {
int32 x, y, z;
angle rotation;
int16 intensity;
union {
struct { uint16 unused:7, clear:1, invisible:1, active:5, collision:1, rendered:1; };
union Flags {
struct { uint16 unused:6, collision:1, invisible:1, once:1, active:5, reverse:1, rendered:1; };
uint16 value;
} flags;
// not exists in file
uint16 align;
int32 modelIndex; // index of representation in models (index + 1) or spriteSequences (-(index + 1)) arrays
void *controller; // Controller implementation or NULL
void *controller; // Controller implementation or NULL
bool isEnemy() {
return type >= ENEMY_TWIN && type <= ENEMY_LARSON;
bool isEnemy() const {
return type >= ENEMY_TWIN && type <= ENEMY_GIANT_MUTANT;
}
bool isBigEnemy() {
bool isBigEnemy() const {
return type == ENEMY_REX || type == ENEMY_MUTANT_1 || type == ENEMY_CENTAUR;
}
bool isDoor() {
bool isDoor() const {
return (type >= DOOR_1 && type <= DOOR_6) || type == DOOR_LIFT;
}
bool isItem() {
bool isItem() const {
return (type >= PISTOLS && type <= AMMO_UZIS) ||
(type >= PUZZLE_1 && type <= PUZZLE_4) ||
(type >= KEY_1 && type <= KEY_4) ||
(type >= KEY_ITEM_1 && type <= KEY_ITEM_4) ||
(type == MEDIKIT_SMALL || type == MEDIKIT_BIG || type == SCION_1); // TODO: recheck all items
}
bool isKeyHole() {
return type >= KEY_HOLE_1 && type <= KEY_HOLE_2;
bool isActor() const {
return type >= CUT_1 && type <= CUT_4;
}
bool isBlock() {
bool isPuzzleHole() const {
return type >= PUZZLE_HOLE_1 && type <= PUZZLE_HOLE_2;
}
bool isBlock() const {
return type >= TR::Entity::BLOCK_1 && type <= TR::Entity::BLOCK_2;
}
bool isLara() const {
return type == LARA;
}
bool castShadow() const {
return isLara() || isEnemy() || isActor();
}
static Type convToInv(Type type) {
switch (type) {
case PISTOLS : return INV_PISTOLS;
@@ -670,10 +679,10 @@ namespace TR {
case PUZZLE_3 : return INV_PUZZLE_3;
case PUZZLE_4 : return INV_PUZZLE_4;
case KEY_1 : return INV_KEY_1;
case KEY_2 : return INV_KEY_2;
case KEY_3 : return INV_KEY_3;
case KEY_4 : return INV_KEY_4;
case KEY_ITEM_1 : return INV_KEY_1;
case KEY_ITEM_2 : return INV_KEY_2;
case KEY_ITEM_3 : return INV_KEY_3;
case KEY_ITEM_4 : return INV_KEY_4;
case LEADBAR : return INV_LEADBAR;
//case TR::Entity::SCION : return TR::Entity::INV_SCION;
@@ -681,21 +690,21 @@ namespace TR {
}
}
static Type getKeyForHole(Type hole) {
static Type getItemForHole(Type hole) {
switch (hole) {
case PUZZLE_HOLE_1 : return PUZZLE_1; break;
case PUZZLE_HOLE_2 : return PUZZLE_2; break;
case PUZZLE_HOLE_3 : return PUZZLE_3; break;
case PUZZLE_HOLE_4 : return PUZZLE_4; break;
case KEY_HOLE_1 : return KEY_1; break;
case KEY_HOLE_2 : return KEY_2; break;
case KEY_HOLE_3 : return KEY_3; break;
case KEY_HOLE_4 : return KEY_4; break;
case PUZZLE_HOLE_1 : return PUZZLE_1; break;
case PUZZLE_HOLE_2 : return PUZZLE_2; break;
case PUZZLE_HOLE_3 : return PUZZLE_3; break;
case PUZZLE_HOLE_4 : return PUZZLE_4; break;
case KEY_HOLE_1 : return KEY_ITEM_1; break;
case KEY_HOLE_2 : return KEY_ITEM_2; break;
case KEY_HOLE_3 : return KEY_ITEM_3; break;
case KEY_HOLE_4 : return KEY_ITEM_4; break;
default : return NONE;
}
}
static void fixOpaque(Type type, bool &opaque) {
static void fixOpaque(Type type, bool &opaque) { // to boost performance on mobile devices
if (type >= LARA && type <= ENEMY_GIANT_MUTANT
&& type != ENEMY_REX
&& type != ENEMY_RAPTOR
@@ -706,6 +715,8 @@ namespace TR {
opaque = true;
if (type == SWITCH || type == SWITCH_WATER)
opaque = true;
if (type == PUZZLE_HOLE_1) // LEVEL3A cogs
opaque = false;
}
};
@@ -818,16 +829,29 @@ namespace TR {
struct ObjectTexture {
uint16 clut;
Tile tile; // tile or palette index
uint16 attribute; // 0 - opaque, 1 - transparent, 2 - blend additive
ubyte2 texCoord[4];
Tile tile; // tile or palette index
uint16 attribute:15, repeat:1; // 0 - opaque, 1 - transparent, 2 - blend additive,
short2 texCoord[4];
short4 getMinMax() const {
return {
min(min(texCoord[0].x, texCoord[1].x), texCoord[2].x),
min(min(texCoord[0].y, texCoord[1].y), texCoord[2].y),
max(max(texCoord[0].x, texCoord[1].x), texCoord[2].x),
max(max(texCoord[0].y, texCoord[1].y), texCoord[2].y),
};
}
};
struct SpriteTexture {
uint16 clut;
uint16 tile;
int16 l, t, r, b;
ubyte2 texCoord[2];
short2 texCoord[2];
short4 getMinMax() const {
return { texCoord[0].x, texCoord[0].y, texCoord[1].x, texCoord[1].y };
}
};
struct SpriteSequence {
@@ -843,7 +867,9 @@ namespace TR {
int16 room; // for camera
int16 speed; // for sink (underwater current)
};
uint16 flags;
struct {
uint16 :8, once:1, :5, :2;
} flags;
};
struct CameraFrame {
@@ -891,13 +917,77 @@ namespace TR {
} flags;
};
#pragma pack(pop)
enum Version : uint32 {
VER_TR1_PC = 0x00000020,
VER_TR1_PSX = 0x56414270,
};
enum LevelID : uint32 {
LEVEL_CUSTOM,
TITLE,
GYM,
LEVEL_1,
LEVEL_2,
LEVEL_3A,
LEVEL_3B,
CUTSCENE_1,
LEVEL_4,
LEVEL_5,
LEVEL_6,
LEVEL_7A,
LEVEL_7B,
CUTSCENE_2,
LEVEL_8A,
LEVEL_8B,
LEVEL_8C,
LEVEL_10A,
CUTSCENE_3,
LEVEL_10B,
CUTSCENE_4,
LEVEL_10C,
LEVEL_EGYPT,
LEVEL_CAT,
LEVEL_END,
LEVEL_END2,
LEVEL_MAX,
};
struct {
const char *name;
const char *title;
int ambientTrack;
} LEVEL_INFO[LEVEL_MAX] = {
{ "" , "Custom Level", TRACK_CAVES },
{ "TITLE" , "", TRACK_TITLE },
{ "GYM" , "Lara's Home", 0 },
{ "LEVEL1" , "Caves", TRACK_CAVES },
{ "LEVEL2" , "City of Vilcabamba", TRACK_CAVES },
{ "LEVEL3A" , "Lost Valley", TRACK_CAVES },
{ "LEVEL3B" , "Tomb of Qualopec", TRACK_CAVES },
{ "CUT1" , "", TRACK_CUT1 },
{ "LEVEL4" , "St. Francis' Folly", TRACK_CAVES },
{ "LEVEL5" , "Colosseum", TRACK_CAVES },
{ "LEVEL6" , "Palace Midas", TRACK_CAVES },
{ "LEVEL7A" , "The Cistern", TRACK_CISTERN },
{ "LEVEL7B" , "Tomb of Tihocan", TRACK_CISTERN },
{ "CUT2" , "", TRACK_CUT2 },
{ "LEVEL8A" , "City of Khamoon", TRACK_EGYPT },
{ "LEVEL8B" , "Obelisk of Khamoon", TRACK_EGYPT },
{ "LEVEL8C" , "Sanctuary of the Scion", TRACK_EGYPT },
{ "LEVEL10A" , "Natla's Mines", TRACK_MINE },
{ "CUT3" , "", TRACK_CUT3 },
{ "LEVEL10B" , "Atlantis", TRACK_MINE },
{ "CUT4" , "", TRACK_CUT4 },
{ "LEVEL10C" , "The Great Pyramid", TRACK_MINE },
{ "EGYPT" , "Return to Egypt", TRACK_EGYPT },
{ "CAT" , "Temple of the Cat", TRACK_EGYPT },
{ "END" , "Atlantean Stronghold", TRACK_EGYPT },
{ "END2" , "The Hive", TRACK_EGYPT },
};
struct Level {
enum : uint32 {
VER_TR1_PC = 0x00000020,
VER_TR1_PSX = 0x56414270,
} version;
Version version;
LevelID id;
int32 tilesCount;
Tile32 *tiles;
@@ -1001,7 +1091,6 @@ namespace TR {
ANTIPAD ,
COMBAT ,
DUMMY ,
ANTI ,
};
struct FloorInfo {
@@ -1031,11 +1120,18 @@ namespace TR {
}
};
Flags flipmap[MAX_FLIPMAP_COUNT];
bool secrets[MAX_SECRETS_COUNT];
Flags tracks[MAX_TRACKS_COUNT];
void *cameraController;
void *laraController;
int cutEntity;
mat4 cutMatrix;
bool isDemoLevel;
bool isHomeLevel;
bool isFlipped;
struct {
int16 muzzleFlash;
@@ -1067,7 +1163,7 @@ namespace TR {
int16 glyphSeq;
} extra;
Level(Stream &stream, bool demo) {
Level(Stream &stream) {
int startPos = stream.pos;
memset(this, 0, sizeof(*this));
cutEntity = -1;
@@ -1088,6 +1184,8 @@ namespace TR {
return;
}
id = getLevelID(stream.size);
if (version == VER_TR1_PSX) {
uint32 offsetTexTiles;
stream.seek(8);
@@ -1206,14 +1304,24 @@ namespace TR {
// models
stream.read(modelsCount);
models = modelsCount ? new Model[modelsCount] : NULL;
for (int i = 0; i < modelsCount; i++)
stream.raw(&models[i], sizeof(models[i]) - (version == VER_TR1_PC ? sizeof(models[i].align) : 0));
for (int i = 0; i < modelsCount; i++) {
Model &m = models[i];
stream.read(m.type);
stream.read(m.unused);
stream.read(m.mCount);
stream.read(m.mStart);
stream.read(m.node);
stream.read(m.frame);
stream.read(m.animation);
if (version == VER_TR1_PSX)
stream.seek(sizeof(m.align));
}
stream.read(staticMeshes, stream.read(staticMeshesCount));
// textures & UV
readObjectTex(stream);
readSpriteTex(stream);
// palette for demo levels
if (version == VER_TR1_PC && demo) stream.read(palette, 256);
if (version == VER_TR1_PC && isDemoLevel) stream.read(palette, 256);
// cameras
stream.read(cameras, stream.read(camerasCount));
// sound sources
@@ -1234,10 +1342,19 @@ namespace TR {
entities = new Entity[entitiesCount];
for (int i = 0; i < entitiesBaseCount; i++) {
Entity &e = entities[i];
stream.raw(&e, sizeof(e) - sizeof(e.align) - sizeof(e.controller) - sizeof(e.modelIndex));
stream.read(e.type);
stream.read(e.room);
stream.read(e.x);
stream.read(e.y);
stream.read(e.z);
stream.read(e.rotation);
stream.read(e.intensity);
stream.read(e.flags);
e.align = 0;
e.controller = NULL;
e.modelIndex = getModelIndex(e.type);
if (e.type == Entity::CUT_1)
cutEntity = i;
}
@@ -1249,7 +1366,7 @@ namespace TR {
if (version == VER_TR1_PC) {
stream.seek(32 * 256);
// palette for release levels
if (!demo)
if (!isDemoLevel)
stream.read(palette, 256);
// cinematic frames for cameras (PC)
stream.read(cameraFrames, stream.read(cameraFramesCount));
@@ -1273,11 +1390,19 @@ namespace TR {
initRoomMeshes();
initTiles(tiles4, tiles8, palette, cluts);
//delete[] tiles4; tiles4 = NULL;
delete[] tiles8; tiles8 = NULL;
//delete[] tiles4;
//tiles4 = NULL;
delete[] tiles8;
tiles8 = NULL;
// init flipmap states
isFlipped = false;
memset(flipmap, 0, MAX_FLIPMAP_COUNT * sizeof(flipmap[0]));
// init secrets states
memset(secrets, 0, MAX_SECRETS_COUNT * sizeof(secrets[0]));
// init soundtracks states
memset(tracks, 0, MAX_TRACKS_COUNT * sizeof(tracks[0]));
// get special models indices
memset(&extra, 0xFF, sizeof(extra));
@@ -1340,26 +1465,7 @@ namespace TR {
}
ASSERT(extra.glyphSeq != -1);
// init cutscene transform
cutMatrix.identity();
if (cutEntity > -1) {
Entity &e = entities[cutEntity];
switch (cameraFramesCount) { // HACK to detect cutscene level number
case 1600 : // CUT1
cutMatrix.translate(vec3(36668, float(e.y), 63180));
cutMatrix.rotateY(-23312.0f / float(0x4000) * PI * 0.5f);
break;
case 1000 : // CUT2
cutMatrix.translate(vec3(51962, float(e.y), 53760));
cutMatrix.rotateY(16380.0f / float(0x4000) * PI * 0.5f);
break;
case 400 : // CUT3
case 1890 : // CUT4
cutMatrix.translate(vec3(float(e.x), float(e.y), float(e.z)));
cutMatrix.rotateY(PI * 0.5f);
break;
}
}
initCutscene();
}
~Level() {
@@ -1413,6 +1519,86 @@ namespace TR {
delete[] soundSize;
}
LevelID getLevelID(int size) {
isDemoLevel = false;
isHomeLevel = false;
switch (size) {
case 508614 :
case 316460 : return TITLE;
case 1074234 :
case 3237128 : isHomeLevel = true; return GYM;
case 1448896 :
case 2533634 : return LEVEL_1;
case 2873406 : isDemoLevel = true; return LEVEL_2;
case 1535734 :
case 2873450 : return LEVEL_2;
case 1630560 :
case 2934730 : return LEVEL_3A;
case 1506614 :
case 2738258 : return LEVEL_3B;
case 722402 :
case 599840 : return CUTSCENE_1;
case 1621970 :
case 3030872 : return LEVEL_4;
case 1585942 :
case 2718540 : return LEVEL_5;
case 1708464 :
case 3074376 : return LEVEL_6;
case 1696664 :
case 2817612 : return LEVEL_7A;
case 1733274 :
case 3389096 : return LEVEL_7B;
case 542960 :
case 354320 : return CUTSCENE_2;
case 1563356 :
case 2880564 : return LEVEL_8A;
case 1565630 :
case 2886756 : return LEVEL_8B;
case 1619360 :
case 3105450 : return LEVEL_8C;
case 1678018 :
case 3224138 : return LEVEL_10A;
case 636660 :
case 512104 : return CUTSCENE_3;
case 1686748 :
case 3094020 : return LEVEL_10B;
case 940398 :
case 879582 : return CUTSCENE_4;
case 1814278 :
case 3532024 : return LEVEL_10C;
case 3279242 : return LEVEL_EGYPT;
case 3270998 : return LEVEL_CAT;
case 3208018 : return LEVEL_END;
case 3153300 : return LEVEL_END2;
}
return LEVEL_CUSTOM;
}
void initCutscene() {
// init cutscene transform
cutMatrix.identity();
if (cutEntity > -1) {
Entity &e = entities[cutEntity];
switch (id) { // HACK to detect cutscene level number
case CUTSCENE_1 :
cutMatrix.translate(vec3(36668, float(e.y), 63180));
cutMatrix.rotateY(-23312.0f / float(0x4000) * PI * 0.5f);
break;
case CUTSCENE_2 :
cutMatrix.translate(vec3(51962, float(e.y), 53760));
cutMatrix.rotateY(16380.0f / float(0x4000) * PI * 0.5f);
break;
case CUTSCENE_3 :
isFlipped = true;
case CUTSCENE_4 :
cutMatrix.translate(vec3(float(e.x), float(e.y), float(e.z)));
cutMatrix.rotateY(PI * 0.5f);
break;
default : ;
}
}
}
void readMeshes(Stream &stream) {
uint32 meshDataSize;
stream.read(meshDataSize);
@@ -1492,8 +1678,8 @@ namespace TR {
stream.seek(sizeof(uint16)); stream.raw(&mesh.rectangles[rCount], crCount * sizeof(Rectangle));
stream.seek(sizeof(uint16)); stream.raw(&mesh.triangles[tCount], ctCount * sizeof(Triangle));
// add "use palette color" flags
for (int i = rCount; i < mesh.rCount; i++) mesh.rectangles[i].texture |= 0x8000;
for (int i = tCount; i < mesh.tCount; i++) mesh.triangles[i].texture |= 0x8000;
for (int i = rCount; i < mesh.rCount; i++) mesh.rectangles[i].color = true;
for (int i = tCount; i < mesh.tCount; i++) mesh.triangles[i].color = true;
break;
}
case VER_TR1_PSX : {
@@ -1531,8 +1717,8 @@ namespace TR {
stream.read(mesh.rectangles, stream.read(mesh.rCount));
stream.read(mesh.triangles, stream.read(mesh.tCount));
for (int i = 0; i < mesh.rCount; i++) if (mesh.rectangles[i].texture < 300) mesh.rectangles[i].texture |= 0x8000;
for (int i = 0; i < mesh.tCount; i++) if (mesh.triangles[i].texture < 300) mesh.triangles[i].texture |= 0x8000;
for (int i = 0; i < mesh.rCount; i++) if (mesh.rectangles[i].texture < 256) mesh.rectangles[i].color = true;
for (int i = 0; i < mesh.tCount; i++) if (mesh.triangles[i].texture < 256) mesh.triangles[i].color = true;
break;
}
}
@@ -1627,7 +1813,7 @@ namespace TR {
uint8 x0, y0;
uint16 clut;
uint8 x1, y1;
Tile tile;
Tile tile;
uint8 x2, y2;
uint16 unknown;
uint8 x3, y3;
@@ -1658,7 +1844,7 @@ namespace TR {
for (int i = 0; i < spriteTexturesCount; i++) {
SpriteTexture &t = spriteTextures[i];
switch (version) {
case VER_TR1_PC : {
case VER_TR1_PC : {
struct {
uint16 tile;
uint8 u, v;
@@ -1705,7 +1891,7 @@ namespace TR {
void initTiles(Tile4 *tiles4, Tile8 *tiles8, Color24 *palette, CLUT *cluts) {
tiles = new Tile32[tilesCount];
// convert to RGBA
switch (version) {
case VER_TR1_PC : {
ASSERT(tiles8);
@@ -1750,7 +1936,7 @@ namespace TR {
int maxY = max(max(t.texCoord[0].y, t.texCoord[1].y), t.texCoord[2].y);
for (int y = minY; y <= maxY; y++)
for (int x = minX; x <= maxX; x++)
for (int x = minX; x <= maxX; x++)
dst.color[y * 256 + x] = clut.color[(x % 2) ? src.index[(y * 256 + x) / 2].b : src.index[(y * 256 + x) / 2].a];
}
@@ -1760,8 +1946,8 @@ namespace TR {
Tile32 &dst = tiles[t.tile];
Tile4 &src = tiles4[t.tile];
for (int y = t.texCoord[0].y; y < t.texCoord[1].y; y++)
for (int x = t.texCoord[0].x; x < t.texCoord[1].x; x += 2) {
for (int y = t.texCoord[0].y; y <= t.texCoord[1].y; y++)
for (int x = t.texCoord[0].x; x <= t.texCoord[1].x; x += 2) {
dst.color[y * 256 + x + 0] = clut.color[src.index[(y * 256 + x) / 2].a];
dst.color[y * 256 + x + 1] = clut.color[src.index[(y * 256 + x) / 2].b];
}
@@ -1777,6 +1963,7 @@ namespace TR {
switch (version) {
case VER_TR1_PC : return palette[texture & 0xFF];
case VER_TR1_PSX : {
ASSERT((texture & 0x7FFF) < 256);
ObjectTexture &t = objectTextures[texture & 0x7FFF];
int idx = (t.texCoord[0].y * 256 + t.texCoord[0].x) / 2;
int part = t.texCoord[0].x % 2;
@@ -1789,6 +1976,7 @@ namespace TR {
}
Stream* getSampleStream(int index) const {
if (!soundOffsets) return NULL;
uint8 *data = &soundData[soundOffsets[index]];
uint32 size = 0;
switch (version) {
@@ -1843,8 +2031,22 @@ namespace TR {
entities[entityIndex].controller = NULL;
}
int getNextRoom(int floorIndex) const {
if (!floorIndex) return NO_ROOM;
FloorData *fd = &floors[floorIndex];
// floor data always in this order and can't be less than uint16 x 3
if (fd->cmd.func == FloorData::FLOOR) fd += 2; // skip floor slant info
if (fd->cmd.func == FloorData::CEILING) fd += 2; // skip ceiling slant info
if (fd->cmd.func == FloorData::PORTAL) return (++fd)->data;
return NO_ROOM;
}
Room::Sector& getSector(int roomIndex, int x, int z, int &dx, int &dz) const {
ASSERT(roomIndex >= 0 && roomIndex < roomsCount);
if (isFlipped && rooms[roomIndex].alternateRoom > -1)
roomIndex = rooms[roomIndex].alternateRoom;
Room &room = rooms[roomIndex];
int sx = x - room.info.x;
@@ -1861,6 +2063,57 @@ namespace TR {
return room.sectors[sx * room.zSectors + sz];
}
Room::Sector& getSector(int roomIndex, int x, int z, int &sectorIndex) {
ASSERT(roomIndex >= 0 && roomIndex < roomsCount);
Room &room = rooms[roomIndex];
x -= room.info.x;
z -= room.info.z;
x /= 1024;
z /= 1024;
return room.sectors[sectorIndex = (x * room.zSectors + z)];
}
Room::Sector* getSector(int &roomIndex, int x, int y, int z) const {
ASSERT(roomIndex >= 0 && roomIndex <= roomsCount);
Room::Sector *sector = NULL;
// check horizontal
while (1) { // Let's Rock!
TR::Room &room = rooms[roomIndex];
int sx = (x - room.info.x) >> 10;
int sz = (z - room.info.z) >> 10;
if (sz <= 0 || sz >= room.xSectors - 1) {
sx = clamp(sx, 1, room.xSectors - 2);
sz = clamp(sz, 0, room.zSectors - 1);
} else
sx = clamp(sx, 0, room.xSectors - 1);
sector = room.sectors + sx * room.zSectors + sz;
int nextRoom = getNextRoom(sector->floorIndex);
if (nextRoom == NO_ROOM)
break;
roomIndex = nextRoom;
};
// check vertical
while (sector->roomAbove != NO_ROOM && y < sector->ceiling * 256) {
TR::Room &room = rooms[roomIndex = sector->roomAbove];
sector = room.sectors + (x - room.info.x) / 1024 * room.zSectors + (z - room.info.z) / 1024;
}
while (sector->roomBelow != NO_ROOM && y >= sector->floor * 256) {
TR::Room &room = rooms[roomIndex = sector->roomBelow];
sector = room.sectors + (x - room.info.x) / 1024 * room.zSectors + (z - room.info.z) / 1024;
}
return sector;
}
void getFloorInfo(int roomIndex, int x, int y, int z, FloorInfo &info) const {
int dx, dz;
Room::Sector &s = getSector(roomIndex, x, z, dx, dz);
@@ -1880,7 +2133,7 @@ namespace TR {
info.trigger = Trigger::ACTIVATE;
info.trigCmdCount = 0;
if (s.floor == -127)
if (s.floor == NO_FLOOR)
return;
Room::Sector *sBelow = &s;
@@ -1912,6 +2165,10 @@ namespace TR {
for (int i = 0; i < info.trigCmdCount; i++) {
FloorData::TriggerCommand cmd = info.trigCmd[i];
if (cmd.action == Action::CAMERA_SWITCH) {
i++;
continue;
}
if (cmd.action != Action::ACTIVATE) continue;
Entity &e = entities[cmd.args];
@@ -2024,10 +2281,6 @@ namespace TR {
}; // struct Level
bool castShadow(Entity::Type type) {
return (type >= Entity::ENEMY_TWIN && type <= Entity::ENEMY_GIANT_MUTANT) || type == Entity::LARA || (type >= Entity::CUT_1 && type <= Entity::CUT_4);
}
}
#endif
#endif

View File

@@ -3,20 +3,40 @@
#include "core.h"
#include "format.h"
#include "cache.h"
#include "level.h"
#include "ui.h"
namespace Game {
Level *level;
ShaderCache *shaderCache;
void startLevel(Stream *lvl, Stream *snd, bool demo, bool home) {
namespace Game {
Level *level;
Stream *nextLevel;
}
void loadAsync(Stream *stream, void *userData) {
if (!stream) {
if (Game::level) Game::level->isEnded = false;
return;
}
Game::nextLevel = stream;
}
namespace Game {
void startLevel(Stream *lvl) {
delete level;
level = new Level(*lvl, snd, demo, home);
UI::init(level);
level = new Level(*lvl);
UI::game = level;
delete lvl;
}
void init(Stream *lvl, Stream *snd) {
void stopChannel(Sound::Sample *channel) {
if (level) level->stopChannel(channel);
}
void init(Stream *lvl) {
nextLevel = NULL;
Core::init();
Core::settings.detail.ambient = true;
@@ -24,30 +44,36 @@ namespace Game {
Core::settings.detail.shadows = true;
Core::settings.detail.water = Core::support.texFloat || Core::support.texHalf;
Core::settings.detail.contact = false;
#ifdef __RPI__
Core::settings.detail.ambient = false;
Core::settings.detail.shadows = false;
#endif
Core::settings.controls.retarget = true;
Core::settings.audio.reverb = true;
shaderCache = new ShaderCache();
UI::init(level);
Sound::callback = stopChannel;
level = NULL;
startLevel(lvl, snd, false, false);
startLevel(lvl);
}
void init(char *lvlName = NULL, char *sndName = NULL) {
if (!lvlName) lvlName = (char*)"LEVEL2.PSX";
#ifndef __EMSCRIPTEN__
if (!sndName) sndName = (char*)"05.ogg";
#endif
init(new Stream(lvlName), sndName ? new Stream(sndName) : NULL);
if (!lvlName) lvlName = (char*)"level/TITLE.PSX";
init(new Stream(lvlName));
}
void free() {
delete level;
UI::free();
delete shaderCache;
Core::free();
}
void updateTick() {
if (Input::state[cInventory])
level->inventory.toggle();
float dt = Core::deltaTime;
if (Input::down[ikR]) // slow motion (for animation debugging)
Core::deltaTime /= 10.0f;
@@ -62,6 +88,16 @@ namespace Game {
}
void update(float delta) {
PROFILE_MARKER("UPDATE");
if (nextLevel) {
startLevel(nextLevel);
nextLevel = NULL;
}
if (level->isEnded)
return;
Input::update();
if (Input::down[ikV]) { // third <-> first person view
@@ -80,6 +116,7 @@ namespace Game {
}
void render() {
PROFILE_MARKER("RENDER");
PROFILE_TIMING(Core::stats.tFrame);
Core::beginFrame();
level->render();
@@ -93,4 +130,4 @@ namespace Game {
}
}
#endif
#endif

View File

@@ -29,58 +29,62 @@ struct Inventory {
Page page, targetPage;
int itemsCount;
TR::LevelID nextLevel; // toggle result
struct Item {
TR::Entity::Type type;
int count;
float angle;
Animation *anim;
int value;
struct Desc {
const char *name;
StringID str;
Page page;
int model;
} desc;
Item() : anim(NULL) {}
Item(TR::Level *level, TR::Entity::Type type, int count = 1) : type(type), count(count), angle(0.0f) {
Item(TR::Level *level, TR::Entity::Type type, int count = 1) : type(type), count(count), angle(0.0f), value(0) {
switch (type) {
case TR::Entity::INV_PASSPORT : desc = { "Game", PAGE_OPTION, level->extra.inv.passport }; break;
case TR::Entity::INV_PASSPORT_CLOSED : desc = { "Game", PAGE_OPTION, level->extra.inv.passport_closed }; break;
case TR::Entity::INV_MAP : desc = { "Map", PAGE_INVENTORY, level->extra.inv.map }; break;
case TR::Entity::INV_COMPASS : desc = { "Compass", PAGE_INVENTORY, level->extra.inv.compass }; break;
case TR::Entity::INV_HOME : desc = { "Lara's Home", PAGE_OPTION, level->extra.inv.home }; break;
case TR::Entity::INV_DETAIL : desc = { "Detail Levels", PAGE_OPTION, level->extra.inv.detail }; break;
case TR::Entity::INV_SOUND : desc = { "Sound", PAGE_OPTION, level->extra.inv.sound }; break;
case TR::Entity::INV_CONTROLS : desc = { "Controls", PAGE_OPTION, level->extra.inv.controls }; break;
case TR::Entity::INV_GAMMA : desc = { "Gamma", PAGE_OPTION, level->extra.inv.gamma }; break;
case TR::Entity::INV_PASSPORT : desc = { STR_GAME, PAGE_OPTION, level->extra.inv.passport }; break;
case TR::Entity::INV_PASSPORT_CLOSED : desc = { STR_GAME, PAGE_OPTION, level->extra.inv.passport_closed }; break;
case TR::Entity::INV_MAP : desc = { STR_MAP, PAGE_INVENTORY, level->extra.inv.map }; break;
case TR::Entity::INV_COMPASS : desc = { STR_COMPASS, PAGE_INVENTORY, level->extra.inv.compass }; break;
case TR::Entity::INV_HOME : desc = { STR_HOME, PAGE_OPTION, level->extra.inv.home }; break;
case TR::Entity::INV_DETAIL : desc = { STR_DETAIL, PAGE_OPTION, level->extra.inv.detail }; break;
case TR::Entity::INV_SOUND : desc = { STR_SOUND, PAGE_OPTION, level->extra.inv.sound }; break;
case TR::Entity::INV_CONTROLS : desc = { STR_CONTROLS, PAGE_OPTION, level->extra.inv.controls }; break;
case TR::Entity::INV_GAMMA : desc = { STR_GAMMA, PAGE_OPTION, level->extra.inv.gamma }; break;
case TR::Entity::INV_PISTOLS : desc = { "Pistols", PAGE_INVENTORY, level->extra.inv.weapon[0] }; break;
case TR::Entity::INV_SHOTGUN : desc = { "Shotgun", PAGE_INVENTORY, level->extra.inv.weapon[1] }; break;
case TR::Entity::INV_MAGNUMS : desc = { "Magnums", PAGE_INVENTORY, level->extra.inv.weapon[2] }; break;
case TR::Entity::INV_UZIS : desc = { "Uzis", PAGE_INVENTORY, level->extra.inv.weapon[3] }; break;
case TR::Entity::INV_PISTOLS : desc = { STR_PISTOLS, PAGE_INVENTORY, level->extra.inv.weapon[0] }; break;
case TR::Entity::INV_SHOTGUN : desc = { STR_SHOTGUN, PAGE_INVENTORY, level->extra.inv.weapon[1] }; break;
case TR::Entity::INV_MAGNUMS : desc = { STR_MAGNUMS, PAGE_INVENTORY, level->extra.inv.weapon[2] }; break;
case TR::Entity::INV_UZIS : desc = { STR_UZIS, PAGE_INVENTORY, level->extra.inv.weapon[3] }; break;
case TR::Entity::INV_AMMO_PISTOLS : desc = { "Pistol Clips", PAGE_INVENTORY, level->extra.inv.ammo[0] }; break;
case TR::Entity::INV_AMMO_SHOTGUN : desc = { "Shotgun Shells", PAGE_INVENTORY, level->extra.inv.ammo[1] }; break;
case TR::Entity::INV_AMMO_MAGNUMS : desc = { "Magnum Clips", PAGE_INVENTORY, level->extra.inv.ammo[2] }; break;
case TR::Entity::INV_AMMO_UZIS : desc = { "Uzi Clips", PAGE_INVENTORY, level->extra.inv.ammo[3] }; break;
case TR::Entity::INV_AMMO_PISTOLS : desc = { STR_AMMO_PISTOLS, PAGE_INVENTORY, level->extra.inv.ammo[0] }; break;
case TR::Entity::INV_AMMO_SHOTGUN : desc = { STR_AMMO_SHOTGUN, PAGE_INVENTORY, level->extra.inv.ammo[1] }; break;
case TR::Entity::INV_AMMO_MAGNUMS : desc = { STR_AMMO_MAGNUMS, PAGE_INVENTORY, level->extra.inv.ammo[2] }; break;
case TR::Entity::INV_AMMO_UZIS : desc = { STR_AMMO_UZIS, PAGE_INVENTORY, level->extra.inv.ammo[3] }; break;
case TR::Entity::INV_MEDIKIT_SMALL : desc = { "Small Medi Pack", PAGE_INVENTORY, level->extra.inv.medikit[0] }; break;
case TR::Entity::INV_MEDIKIT_BIG : desc = { "Large Medi Pack", PAGE_INVENTORY, level->extra.inv.medikit[1] }; break;
case TR::Entity::INV_MEDIKIT_SMALL : desc = { STR_MEDI_SMALL, PAGE_INVENTORY, level->extra.inv.medikit[0] }; break;
case TR::Entity::INV_MEDIKIT_BIG : desc = { STR_MEDI_BIG, PAGE_INVENTORY, level->extra.inv.medikit[1] }; break;
case TR::Entity::INV_PUZZLE_1 : desc = { "Puzzle", PAGE_ITEMS, level->extra.inv.puzzle[0] }; break;
case TR::Entity::INV_PUZZLE_2 : desc = { "Puzzle", PAGE_ITEMS, level->extra.inv.puzzle[1] }; break;
case TR::Entity::INV_PUZZLE_3 : desc = { "Puzzle", PAGE_ITEMS, level->extra.inv.puzzle[2] }; break;
case TR::Entity::INV_PUZZLE_4 : desc = { "Puzzle", PAGE_ITEMS, level->extra.inv.puzzle[3] }; break;
case TR::Entity::INV_PUZZLE_1 : desc = { STR_PUZZLE, PAGE_ITEMS, level->extra.inv.puzzle[0] }; break;
case TR::Entity::INV_PUZZLE_2 : desc = { STR_PUZZLE, PAGE_ITEMS, level->extra.inv.puzzle[1] }; break;
case TR::Entity::INV_PUZZLE_3 : desc = { STR_PUZZLE, PAGE_ITEMS, level->extra.inv.puzzle[2] }; break;
case TR::Entity::INV_PUZZLE_4 : desc = { STR_PUZZLE, PAGE_ITEMS, level->extra.inv.puzzle[3] }; break;
case TR::Entity::INV_KEY_1 : desc = { "Key", PAGE_ITEMS, level->extra.inv.key[0] }; break;
case TR::Entity::INV_KEY_2 : desc = { "Key", PAGE_ITEMS, level->extra.inv.key[1] }; break;
case TR::Entity::INV_KEY_3 : desc = { "Key", PAGE_ITEMS, level->extra.inv.key[2] }; break;
case TR::Entity::INV_KEY_4 : desc = { "Key", PAGE_ITEMS, level->extra.inv.key[3] }; break;
case TR::Entity::INV_KEY_1 : desc = { STR_KEY, PAGE_ITEMS, level->extra.inv.key[0] }; break;
case TR::Entity::INV_KEY_2 : desc = { STR_KEY, PAGE_ITEMS, level->extra.inv.key[1] }; break;
case TR::Entity::INV_KEY_3 : desc = { STR_KEY, PAGE_ITEMS, level->extra.inv.key[2] }; break;
case TR::Entity::INV_KEY_4 : desc = { STR_KEY, PAGE_ITEMS, level->extra.inv.key[3] }; break;
case TR::Entity::INV_LEADBAR : desc = { "Lead Bar", PAGE_ITEMS, level->extra.inv.leadbar }; break;
case TR::Entity::INV_SCION : desc = { "Scion", PAGE_ITEMS, level->extra.inv.scion }; break;
default : desc = { "unknown", PAGE_ITEMS, -1 }; break;
case TR::Entity::INV_LEADBAR : desc = { STR_LEAD_BAR, PAGE_ITEMS, level->extra.inv.leadbar }; break;
case TR::Entity::INV_SCION : desc = { STR_SCION, PAGE_ITEMS, level->extra.inv.scion }; break;
default : desc = { STR_UNKNOWN, PAGE_ITEMS, -1 }; break;
}
if (desc.model > -1) {
@@ -108,7 +112,19 @@ struct Inventory {
}
void update() {
if (anim) anim->update();
if (!anim) return;
anim->update();
if (type == TR::Entity::INV_PASSPORT) {
float t = (14 + value * 5) / 30.0f;
if ( (anim->dir > 0.0f && anim->time > t) ||
(anim->dir < 0.0f && anim->time < t)) {
anim->dir = 0.0f;
anim->time = t;
anim->updateInfo();
}
}
}
void render(IGame *game, const Basis &basis) {
@@ -126,39 +142,61 @@ struct Inventory {
}
void choose() {
if (anim) anim->setAnim(0, 0, false);
if (!anim) return;
anim->setAnim(0, 0, false);
}
} *items[INVENTORY_MAX_ITEMS];
Inventory(IGame *game) : game(game), active(false), chosen(false), index(0), targetIndex(0), page(PAGE_OPTION), targetPage(PAGE_OPTION), itemsCount(0) {
static void loadTitleBG(Stream *stream, void *userData) {
if (!stream) return;
Inventory *inv = (Inventory*)userData;
inv->background[0] = Texture::LoadPCX(*stream);
delete stream;
}
Inventory(IGame *game) : game(game), active(false), chosen(false), index(0), targetIndex(0), page(PAGE_OPTION), targetPage(PAGE_OPTION), itemsCount(0), nextLevel(TR::LEVEL_MAX) {
TR::LevelID id = game->getLevel()->id;
add(TR::Entity::INV_PASSPORT);
add(TR::Entity::INV_DETAIL);
add(TR::Entity::INV_SOUND);
add(TR::Entity::INV_CONTROLS);
/*
add(TR::Entity::INV_COMPASS);
if (level->extra.inv.map != -1)
add(TR::Entity::INV_MAP);
if (level->extra.inv.gamma != -1)
add(TR::Entity::INV_GAMMA);
*/
add(TR::Entity::INV_PISTOLS, UNLIMITED_AMMO);
add(TR::Entity::INV_SHOTGUN, 10);
add(TR::Entity::INV_MAGNUMS, 10);
add(TR::Entity::INV_UZIS, 50);
// add(TR::Entity::INV_MEDIKIT_SMALL, 999);
// add(TR::Entity::INV_MEDIKIT_BIG, 999);
// add(TR::Entity::INV_SCION, 1);
// add(TR::Entity::INV_KEY_1, 1);
// add(TR::Entity::INV_PUZZLE_1, 1);
if (id != TR::TITLE && id != TR::GYM) {
/*
if (level->extra.inv.map != -1)
add(TR::Entity::INV_MAP);
if (level->extra.inv.gamma != -1)
add(TR::Entity::INV_GAMMA);
*/
add(TR::Entity::INV_PISTOLS, UNLIMITED_AMMO);
add(TR::Entity::INV_SHOTGUN, 10);
add(TR::Entity::INV_MAGNUMS, 10);
add(TR::Entity::INV_UZIS, 50);
// add(TR::Entity::INV_MEDIKIT_SMALL, 999);
// add(TR::Entity::INV_MEDIKIT_BIG, 999);
// add(TR::Entity::INV_SCION, 1);
// add(TR::Entity::INV_KEY_1, 1);
// add(TR::Entity::INV_PUZZLE_1, 1);
}
if (id == TR::TITLE) {
add(TR::Entity::INV_HOME);
memset(background, 0, sizeof(background));
new Stream("level/TITLEH.PCX", loadTitleBG, this);
} else {
add(TR::Entity::INV_COMPASS);
for (int i = 0; i < COUNT(background); i++)
background[i] = new Texture(INVENTORY_BG_SIZE, INVENTORY_BG_SIZE, Texture::RGBA, false);
}
phaseRing = phasePage = phaseChoose = phaseSelect = 0.0f;
memset(pageItemIndex, 0, sizeof(pageItemIndex));
for (int i = 0; i < COUNT(background); i++)
background[i] = new Texture(INVENTORY_BG_SIZE, INVENTORY_BG_SIZE, Texture::RGBA, false);
}
~Inventory() {
@@ -261,7 +299,7 @@ struct Inventory {
}
bool chooseKey(TR::Entity::Type hole) {
TR::Entity::Type type = TR::Entity::getKeyForHole(hole);
TR::Entity::Type type = TR::Entity::getItemForHole(hole);
if (type == TR::Entity::NONE)
return false;
int index = contains(type);
@@ -290,6 +328,7 @@ struct Inventory {
for (int i = 0; i < itemsCount; i++)
items[i]->reset();
nextLevel = TR::LEVEL_MAX;
phasePage = 1.0f;
phaseSelect = 1.0f;
page = targetPage = curPage;
@@ -363,13 +402,29 @@ struct Inventory {
return active && phaseRing == 1.0f && index == targetIndex && phasePage == 1.0f && (type == TR::Entity::INV_MEDIKIT_SMALL || type == TR::Entity::INV_MEDIKIT_BIG);
}
void onChoose(Item *item) {
if (item->type == TR::Entity::INV_PASSPORT) {
game->playSound(TR::SND_INV_PAGE, vec3(), 0, 0);
item->value = 1;
passportSlot = 0;
passportSlotCount = 2;
passportSlots[0] = TR::LEVEL_1;
passportSlots[1] = TR::LEVEL_2;
}
}
void update() {
float lastChoose = phaseChoose;
if (phaseChoose == 0.0f)
doPhase(active, 2.0f, phaseRing);
doPhase(true, 1.6f, phasePage);
doPhase(chosen, 4.0f, phaseChoose);
doPhase(true, 2.5f, phaseSelect);
if (phaseChoose == 1.0f && lastChoose != 1.0f)
onChoose(items[getGlobalIndex(page, index)]);
if (page != targetPage && phasePage == 1.0f) {
page = targetPage;
index = targetIndex = pageItemIndex[page];
@@ -382,20 +437,82 @@ struct Inventory {
bool ready = active && phaseRing == 1.0f && phasePage == 1.0f;
if (index == targetIndex && targetPage == page && ready && !chosen) {
float s = Input::touchTimerVis > 0.0f ? -1.0f : 1.0f;
enum KeyDir { NONE, LEFT, RIGHT, UP, DOWN } dir;
if (Input::state[cLeft] || Input::joy.L.x < -0.5f || Input::joy.R.x > 0.5f) { phaseSelect = 0.0f; targetIndex = (targetIndex - 1 + count) % count; }
if (Input::state[cRight] || Input::joy.L.x > 0.5f || Input::joy.R.x < -0.5f) { phaseSelect = 0.0f; targetIndex = (targetIndex + 1) % count; }
if ((Input::state[cUp] || Input::joy.L.y < -0.5f || Input::joy.R.y > 0.5f) && page < PAGE_ITEMS && getItemsCount(page + 1)) { phasePage = 0.0f; targetPage = Page(page + 1); }
if ((Input::state[cDown] || Input::joy.L.y > 0.5f || Input::joy.R.y < -0.5f) && page > PAGE_OPTION && getItemsCount(page - 1)) { phasePage = 0.0f; targetPage = Page(page - 1); }
if (Input::state[cLeft] || Input::joy.L.x < -0.5f || Input::joy.R.x > 0.5f)
dir = LEFT;
else if (Input::state[cRight] || Input::joy.L.x > 0.5f || Input::joy.R.x < -0.5f)
dir = RIGHT;
else if (Input::state[cUp] || Input::joy.L.y < -0.5f || Input::joy.R.y > 0.5f)
dir = UP;
else if (Input::state[cDown] || Input::joy.L.y > 0.5f || Input::joy.R.y < -0.5f)
dir = DOWN;
else
dir = NONE;
static KeyDir lastDir = NONE;
if (index == targetIndex && targetPage == page && ready) {
if (!chosen) {
if (dir == UP && !(page < PAGE_ITEMS && getItemsCount(page + 1))) dir = NONE;
if (dir == DOWN && !(page > PAGE_OPTION && getItemsCount(page - 1))) dir = NONE;
switch (dir) {
case LEFT : { phaseSelect = 0.0f; targetIndex = (targetIndex - 1 + count) % count; } break;
case RIGHT : { phaseSelect = 0.0f; targetIndex = (targetIndex + 1) % count; } break;
case UP : { phasePage = 0.0f; targetPage = Page(page + 1); } break;
case DOWN : { phasePage = 0.0f; targetPage = Page(page - 1); } break;
default : ;
}
if (index != targetIndex) {
vec3 p;
game->playSound(TR::SND_INV_SPIN, p, 0, 0);
}
} else {
Item *item = items[getGlobalIndex(page, index)];
if (item->type == TR::Entity::INV_PASSPORT && passportSlotCount) {
if (lastDir != dir) {
// passport slots
if (item->value == 0 && item->anim->dir == 0.0f) { // slot select
if (dir == UP) { passportSlot = (passportSlot - 1 + passportSlotCount) % passportSlotCount; };
if (dir == DOWN) { passportSlot = (passportSlot + 1) % passportSlotCount; };
}
// passport pages
if (dir == LEFT && item->value > 0) { item->value--; item->anim->dir = -1.0f; game->playSound(TR::SND_INV_PAGE, vec3(), 0, 0); }
if (dir == RIGHT && item->value < 2) { item->value++; item->anim->dir = 1.0f; game->playSound(TR::SND_INV_PAGE, vec3(), 0, 0); }
lastDir = dir;
}
if (Input::state[cAction] && phaseChoose == 1.0f) {
TR::LevelID id = game->getLevel()->id;
switch (item->value) {
case 0 : nextLevel = passportSlots[passportSlot]; break;
case 1 : nextLevel = (id == TR::TITLE) ? TR::LEVEL_1 : game->getLevel()->id; break;
case 2 : nextLevel = (id == TR::TITLE) ? TR::LEVEL_MAX : TR::TITLE; break;
}
if (nextLevel != TR::LEVEL_MAX) {
item->anim->dir = -1.0f;
item->value = -100;
toggle();
}
}
}
if (item->type == TR::Entity::INV_HOME) {
if (Input::state[cAction] && phaseChoose == 1.0f) {
nextLevel = TR::GYM;
toggle();
}
}
if (index != targetIndex) {
vec3 p;
game->playSound(TR::SND_INV_SPIN, p, 0, 0);
}
}
ready = active && phaseRing == 1.0f && phasePage == 1.0f;
vec3 p;
Item *item = items[getGlobalIndex(page, index)];
@@ -469,6 +586,9 @@ struct Inventory {
toggle();
}
}
if (!isActive() && nextLevel != TR::LEVEL_MAX)
game->loadLevel(nextLevel);
}
void prepareBackground() {
@@ -518,21 +638,66 @@ struct Inventory {
}
}
void renderItemText(const Item *item, float width) {
UI::textOut(vec2(0, 480 - 16), item->desc.name, UI::aCenter, width);
renderItemCount(item, vec2(width / 2 - 160, 480 - 96), 320);
int passportSlot, passportSlotCount;
TR::LevelID passportSlots[32];
void renderPassport(Item *item) {
if (item->value != 0 || item->anim->dir != 0.0f) return; // check for "Load Game" page
float y = 120.0f;
float h = 20.0f;
float w = 320.0f;
// background
UI::renderBar(UI::BAR_OPTION, vec2((UI::width - w - 16.0f) * 0.5f, y - 16.0f), vec2(w + 16.0f, h * 16.0f), 0.0f, 0, 0xC0000000);
// title
UI::renderBar(UI::BAR_OPTION, vec2((UI::width - w) * 0.5f, y - h + 6), vec2(w, h - 6), 1.0f, 0x802288FF, 0, 0, 0);
UI::textOut(vec2(0, y), STR_SELECT_LEVEL, UI::aCenter, UI::width);
y += h * 2;
UI::renderBar(UI::BAR_OPTION, vec2((UI::width - w) * 0.5f, y + passportSlot * h + 6 - h), vec2(w, h - 6), 1.0f, 0xFFD8377C, 0);
for (int i = 0; i < passportSlotCount; i++)
if (passportSlots[i] == TR::LEVEL_MAX)
UI::textOut(vec2(0, y + i * h), STR_AUTOSAVE, UI::aCenter, UI::width);
else
UI::textOut(vec2(0, y + i * h), TR::LEVEL_INFO[passportSlots[i]].title, UI::aCenter, UI::width);
}
void renderItemText(Item *item) {
if (item->type == TR::Entity::INV_PASSPORT && phaseChoose == 1.0f) {
StringID str = STR_LOAD_GAME;
if (game->getLevel()->id == TR::TITLE) {
if (item->value == 1) str = STR_START_GAME;
if (item->value == 2) str = STR_EXIT_GAME;
} else {
if (item->value == 1) str = STR_RESTART_LEVEL;
if (item->value == 2) str = STR_EXIT_TO_TITLE;
}
UI::textOut(vec2(0, 480 - 16), str, UI::aCenter, UI::width);
} else
UI::textOut(vec2(0, 480 - 16), item->desc.str, UI::aCenter, UI::width);
renderItemCount(item, vec2(UI::width / 2 - 160, 480 - 96), 320);
if (phaseChoose == 1.0f) {
if (item->type == TR::Entity::INV_PASSPORT ||
item->type == TR::Entity::INV_MAP ||
item->type == TR::Entity::INV_COMPASS ||
item->type == TR::Entity::INV_HOME ||
item->type == TR::Entity::INV_DETAIL ||
item->type == TR::Entity::INV_SOUND ||
item->type == TR::Entity::INV_CONTROLS ||
item->type == TR::Entity::INV_GAMMA)
{
UI::textOut(vec2(0, 240), "Not implemented yet!", UI::aCenter, width);
switch (item->type) {
case TR::Entity::INV_PASSPORT :
renderPassport(item);
break;
case TR::Entity::INV_HOME :
break;
case TR::Entity::INV_COMPASS :
case TR::Entity::INV_MAP :
case TR::Entity::INV_DETAIL :
case TR::Entity::INV_SOUND :
case TR::Entity::INV_CONTROLS :
case TR::Entity::INV_GAMMA :
UI::textOut(vec2(0, 240), STR_NOT_IMPLEMENTED, UI::aCenter, UI::width);
break;
default : ;
}
}
}
@@ -588,16 +753,34 @@ struct Inventory {
void render() {
// background
Core::setDepthTest(false);
Core::setBlending(bmNone);
game->setShader(Core::passFilter, Shader::FILTER_MIXER, false, false);
Core::active.shader->setParam(uParam, vec4(phaseRing, 1.0f - phaseRing * 0.4f, 0, 0));;
background[0]->bind(sDiffuse); // orignal image
background[1]->bind(sNormal); // blured grayscale image
game->getMesh()->renderQuad();
if (background[0]) {
background[0]->bind(sDiffuse); // orignal image
if (background[1]) {
game->setShader(Core::passFilter, Shader::FILTER_MIXER, false, false);
Core::active.shader->setParam(uParam, vec4(phaseRing, 1.0f - phaseRing * 0.4f, 0, 0));;
background[1]->bind(sNormal); // blured grayscale image
} else {
game->setShader(Core::passFilter, Shader::DEFAULT, false, false);
float aspectSrc = float(640.0f) / float(480.0f);
float aspectDst = float(Core::width) / float(Core::height);
float aspectImg = aspectDst / aspectSrc;
float ax = 640.0f / float(background[0]->width);
float ay = 480.0f / float(background[0]->height);
Core::active.shader->setParam(uParam, vec4(ax * aspectImg, -ay, (0.5f - aspectImg * 0.5f) * ax, ay));
}
game->getMesh()->renderQuad();
}
Core::setDepthTest(true);
Core::setBlending(bmAlpha);
if (game->isCutscene())
return;
// items
game->setupBinding();
@@ -640,9 +823,10 @@ struct Inventory {
void renderUI() {
if (!active || phaseRing < 1.0f) return;
static const char* pageTitle[PAGE_MAX] = { "OPTION", "INVENTORY", "ITEMS" };
static const StringID pageTitle[PAGE_MAX] = { STR_OPTION, STR_INVENTORY, STR_ITEMS };
UI::textOut(vec2( 0, 32), pageTitle[page], UI::aCenter, UI::width);
if (game->getLevel()->id != TR::TITLE)
UI::textOut(vec2( 0, 32), pageTitle[page], UI::aCenter, UI::width);
if (page < PAGE_ITEMS && getItemsCount(page + 1)) {
UI::textOut(vec2(16, 32), "[", UI::aLeft, UI::width);
@@ -655,7 +839,7 @@ struct Inventory {
}
if (index == targetIndex)
renderItemText(items[getGlobalIndex(page, index)], UI::width);
renderItemText(items[getGlobalIndex(page, index)]);
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,9 @@
#include "debug.h"
#endif
extern ShaderCache *shaderCache;
extern void loadAsync(Stream *stream, void *userData);
struct Level : IGame {
TR::Level level;
Inventory inventory;
@@ -33,7 +36,6 @@ struct Level : IGame {
float clipHeight;
} *params = (Params*)&Core::params;
ShaderCache *shaderCache;
AmbientCache *ambientCache;
WaterCache *waterCache;
ZoneCache *zoneCache;
@@ -42,19 +44,50 @@ struct Level : IGame {
Sound::Sample *sndUnderwater;
Sound::Sample *sndCurrent;
int curTrack;
bool lastTitle;
bool isEnded;
TR::Effect effect;
float effectTimer;
int flickerIdx;
// IGame implementation ========
virtual void loadLevel(TR::LevelID id) {
if (isEnded) return;
Sound::stopAll();
isEnded = true;
char buf[64];
buf[0] = 0;
strcat(buf, "level/");
strcat(buf, TR::LEVEL_INFO[id].name);
switch (level.version) {
case TR::VER_TR1_PC : strcat(buf, ".PHD"); break;
case TR::VER_TR1_PSX : strcat(buf, ".PSX"); break;
}
new Stream(buf, loadAsync);
}
virtual TR::Level* getLevel() {
return &level;
}
virtual MeshBuilder* getMesh() {
virtual MeshBuilder* getMesh() {
return mesh;
}
virtual Controller* getCamera() {
return camera;
virtual ICamera* getCamera() {
return camera;
}
virtual Controller* getLara() {
return lara;
}
virtual bool isCutscene() {
if (level.id == TR::TITLE) return false;
return camera->state == Camera::STATE_CUTSCENE;
}
virtual uint16 getRandomBox(uint16 zone, uint16 *zones) {
@@ -85,7 +118,7 @@ struct Level : IGame {
}
virtual void setShader(Core::Pass pass, Shader::Type type, bool underwater = false, bool alphaTest = false) {
shaderCache->bind(pass, type, (underwater ? ShaderCache::FX_UNDERWATER : 0) | (alphaTest ? ShaderCache::FX_ALPHA_TEST : 0) | ((params->clipHeight != NO_CLIP_PLANE && pass == Core::passCompose) ? ShaderCache::FX_CLIP_PLANE : 0));
shaderCache->bind(pass, type, (underwater ? ShaderCache::FX_UNDERWATER : 0) | (alphaTest ? ShaderCache::FX_ALPHA_TEST : 0) | ((params->clipHeight != NO_CLIP_PLANE && pass == Core::passCompose) ? ShaderCache::FX_CLIP_PLANE : 0), this);
}
virtual void setupBinding() {
@@ -97,7 +130,6 @@ struct Level : IGame {
Core::basis.identity();
}
virtual void renderEnvironment(int roomIndex, const vec3 &pos, Texture **targets, int stride = 0) {
PROFILE_MARKER("ENVIRONMENT");
Core::eye = 0.0f;
@@ -113,8 +145,26 @@ struct Level : IGame {
}
}
virtual void fxQuake(float time) {
camera->shake = time;
virtual void setEffect(TR::Effect effect, float param) {
if (effect == TR::Effect::NONE)
return;
if (effect == TR::Effect::FLOOR_SHAKE) {
camera->shake = param;
return;
}
if (effect == TR::Effect::FLICKER)
flickerIdx = 0;
if (effect == TR::Effect::FLOOD) {
Sound::Sample *sample = playSound(TR::SND_FLOOD, vec3(), 0);
if (sample)
sample->setVolume(0.0f, 4.0f);
}
this->effect = effect;
this->effectTimer = 0.0f;
}
virtual bool invUse(TR::Entity::Type type) {
@@ -136,7 +186,7 @@ struct Level : IGame {
}
virtual Sound::Sample* playSound(int id, const vec3 &pos, int flags, int group = -1) const {
if (level.version == TR::Level::VER_TR1_PSX && id == TR::SND_SECRET)
if (level.version == TR::VER_TR1_PSX && id == TR::SND_SECRET)
return NULL;
int16 a = level.soundsMap[id];
@@ -156,18 +206,68 @@ struct Level : IGame {
}
return NULL;
}
void stopChannel(Sound::Sample *channel) {
if (channel == sndSoundtrack) {
if (sndCurrent == sndSoundtrack)
sndCurrent = NULL;
sndSoundtrack = NULL;
playTrack(0);
}
}
static void playAsync(Stream *stream, void *userData) {
if (!stream) return;
Level *level = (Level*)userData;
level->sndSoundtrack = Sound::play(stream, vec3(0.0f), 0.01f, 1.0f, Sound::MUSIC);
if (level->sndSoundtrack)
level->sndSoundtrack->setVolume(1.0f, 0.2f);
}
virtual void playTrack(int track, bool restart = false) {
if (track == 0)
track = TR::LEVEL_INFO[level.id].ambientTrack;
if (curTrack == track) {
if (restart && sndSoundtrack) {
sndSoundtrack->replay();
sndSoundtrack->setVolume(1.0f, 0.2f);
}
return;
}
curTrack = track;
if (track == 0) return;
if (sndSoundtrack) {
sndSoundtrack->setVolume(-1.0f, 0.2f);
if (sndCurrent == sndSoundtrack)
sndCurrent = NULL;
sndSoundtrack = NULL;
}
char title[32];
sprintf(title, "audio/track_%02d.ogg", track);
new Stream(title, playAsync, this);
}
virtual void stopTrack() {
playTrack(0);
}
//==============================
Level(Stream &stream, Stream *snd, bool demo, bool home) : level(stream, demo), inventory(this), lara(NULL) {
Level(Stream &stream) : level(stream), inventory(this), lara(NULL), isEnded(false) {
params->time = 0.0f;
#ifdef _DEBUG
Debug::init();
#endif
mesh = new MeshBuilder(level);
initTextures();
shaderCache = new ShaderCache(this);
mesh = new MeshBuilder(level);
initOverrides();
for (int i = 0; i < level.entitiesBaseCount; i++) {
@@ -175,7 +275,7 @@ struct Level : IGame {
switch (entity.type) {
case TR::Entity::LARA :
case TR::Entity::CUT_1 :
entity.controller = (lara = new Lara(this, i, home));
entity.controller = (lara = new Lara(this, i));
break;
case TR::Entity::ENEMY_WOLF :
entity.controller = new Wolf(this, i);
@@ -231,7 +331,7 @@ struct Level : IGame {
case TR::Entity::GEARS_1 :
case TR::Entity::GEARS_2 :
case TR::Entity::GEARS_3 :
entity.controller = new Boulder(this, i);
entity.controller = new Gear(this, i);
break;
case TR::Entity::TRAP_FLOOR :
entity.controller = new TrapFloor(this, i);
@@ -240,14 +340,16 @@ struct Level : IGame {
entity.controller = new Crystal(this, i);
break;
case TR::Entity::TRAP_BLADE :
entity.controller = new TrapBlade(this, i);
break;
case TR::Entity::TRAP_SPIKES :
entity.controller = new Trigger(this, i, true);
entity.controller = new TrapSpikes(this, i);
break;
case TR::Entity::TRAP_BOULDER :
entity.controller = new Boulder(this, i);
entity.controller = new TrapBoulder(this, i);
break;
case TR::Entity::TRAP_DARTGUN :
entity.controller = new Dartgun(this, i);
entity.controller = new TrapDartgun(this, i);
break;
case TR::Entity::BLOCK_1 :
case TR::Entity::BLOCK_2 :
@@ -258,13 +360,17 @@ struct Level : IGame {
case TR::Entity::MOVING_BLOCK :
entity.controller = new MovingBlock(this, i);
break;
case TR::Entity::FALLING_CEILING_1 :
case TR::Entity::FALLING_CEILING_2 :
case TR::Entity::FALLING_SWORD :
entity.controller = new Trigger(this, i, true);
case TR::Entity::TRAP_CEILING_1 :
case TR::Entity::TRAP_CEILING_2 :
entity.controller = new TrapCeiling(this, i);
break;
case TR::Entity::TRAP_SWORD :
entity.controller = new TrapSword(this, i);
break;
case TR::Entity::SWITCH :
case TR::Entity::SWITCH_WATER :
entity.controller = new Switch(this, i);
break;
case TR::Entity::PUZZLE_HOLE_1 :
case TR::Entity::PUZZLE_HOLE_2 :
case TR::Entity::PUZZLE_HOLE_3 :
@@ -273,7 +379,7 @@ struct Level : IGame {
case TR::Entity::KEY_HOLE_2 :
case TR::Entity::KEY_HOLE_3 :
case TR::Entity::KEY_HOLE_4 :
entity.controller = new Trigger(this, i, false);
entity.controller = new KeyHole(this, i);
break;
case TR::Entity::WATERFALL :
entity.controller = new Waterfall(this, i);
@@ -286,13 +392,12 @@ struct Level : IGame {
}
}
lastTitle = false;
if (!isTitle()) {
if (level.id != TR::TITLE) {
ASSERT(lara != NULL);
camera = new Camera(this, lara);
level.cameraController = camera;
level.laraController = lara;
ambientCache = Core::settings.detail.ambient ? new AmbientCache(this) : NULL;
waterCache = Core::settings.detail.water ? new WaterCache(this) : NULL;
@@ -301,20 +406,19 @@ struct Level : IGame {
initReflections();
// init sounds
//Sound::play(Sound::openWAD("05_Lara's_Themes.wav"), 1, 1, 0);
sndSoundtrack = Sound::play(snd, vec3(0.0f), 1, 1, Sound::Flags::LOOP);
// init sounds
//sndSoundtrack = Sound::play(Sound::openWAD("05_Lara's_Themes.wav"), vec3(0.0f), 1, 1, Sound::Flags::LOOP);
sndUnderwater = lara->playSound(TR::SND_UNDERWATER, vec3(0.0f), Sound::LOOP);
sndUnderwater = lara->playSound(TR::SND_UNDERWATER, vec3(0.0f), Sound::LOOP | Sound::MUSIC);
if (sndUnderwater)
sndUnderwater->volume = 0.0f;
sndCurrent = sndSoundtrack;
sndUnderwater->volume = sndUnderwater->volumeTarget = 0.0f;
for (int i = 0; i < level.soundSourcesCount; i++) {
TR::SoundSource &src = level.soundSources[i];
lara->playSound(src.id, vec3(float(src.x), float(src.y), float(src.z)), Sound::PAN | Sound::LOOP | Sound::STATIC);
}
lastTitle = false;
} else {
camera = NULL;
ambientCache = NULL;
@@ -324,7 +428,15 @@ struct Level : IGame {
sndSoundtrack = NULL;
sndUnderwater = NULL;
sndCurrent = NULL;
lastTitle = true;
inventory.toggle(Inventory::PAGE_OPTION);
}
sndSoundtrack = NULL;
playTrack(curTrack = 0);
sndCurrent = sndSoundtrack;
effect = TR::Effect::NONE;
}
virtual ~Level() {
@@ -334,8 +446,6 @@ struct Level : IGame {
for (int i = 0; i < level.entitiesCount; i++)
delete (Controller*)level.entities[i].controller;
delete shaderCache;
delete shadow;
delete ambientCache;
delete waterCache;
@@ -349,8 +459,126 @@ struct Level : IGame {
Sound::stopAll();
}
bool isTitle() {
return lara == NULL || inventory.isActive();
static void fillCallback(int id, int width, int height, int tileX, int tileY, void *userData, void *data) {
static const uint32 whiteColor = 0xFFFFFFFF;
static const uint32 barColor[UI::BAR_MAX][25] = {
// health bar
{ 0xFF2C5D71, 0xFF5E81AE, 0xFF2C5D71, 0xFF1B4557, 0xFF16304F },
// oxygen bar
{ 0xFF647464, 0xFFA47848, 0xFF647464, 0xFF4C504C, 0xFF303030 },
// option bar
{ 0x00FFFFFF, 0x20FFFFFF, 0x20FFFFFF, 0x20FFFFFF, 0x00FFFFFF,
0x00FFFFFF, 0x60FFFFFF, 0x60FFFFFF, 0x60FFFFFF, 0x00FFFFFF,
0x00FFFFFF, 0x80FFFFFF, 0x80FFFFFF, 0x80FFFFFF, 0x00FFFFFF,
0x00FFFFFF, 0x60FFFFFF, 0x60FFFFFF, 0x60FFFFFF, 0x00FFFFFF,
0x00FFFFFF, 0x20FFFFFF, 0x20FFFFFF, 0x20FFFFFF, 0x00FFFFFF },
};
int stride = 256, uvCount;
short2 *uv = NULL;
TR::Level *level = (TR::Level*)userData;
TR::Color32 *src, *dst = (TR::Color32*)data;
short4 mm;
if (id < level->objectTexturesCount) { // textures
TR::ObjectTexture &t = level->objectTextures[id];
mm = t.getMinMax();
src = level->tiles[t.tile.index].color;
uv = t.texCoord;
uvCount = 4;
} else {
id -= level->objectTexturesCount;
if (id < level->spriteTexturesCount) { // sprites
TR::SpriteTexture &t = level->spriteTextures[id];
mm = t.getMinMax();
src = level->tiles[t.tile].color;
uv = t.texCoord;
uvCount = 2;
} else { // common (generated) textures
id -= level->spriteTexturesCount;
TR::ObjectTexture *tex;
mm.x = mm.y = mm.z = mm.w = 0;
stride = 1;
uvCount = 4;
switch (id) {
case UI::BAR_HEALTH :
case UI::BAR_OXYGEN :
case UI::BAR_OPTION :
src = (TR::Color32*)&barColor[id][0];
tex = &barTile[id];
mm.w = 4; // height - 1
if (id == UI::BAR_OPTION) {
stride = 5;
mm.z = 4;
}
break;
case 3 : // white color
src = (TR::Color32*)&whiteColor;
tex = &whiteTile;
break;
default : return;
}
memset(tex, 0, sizeof(*tex));
uv = tex->texCoord;
uv[2].y += mm.w;
uv[3].y += mm.w;
uv[1].x += mm.z;
uv[2].x += mm.z;
}
}
int cx = -mm.x, cy = -mm.y;
if (data) {
int w = mm.z - mm.x + 1;
int h = mm.w - mm.y + 1;
int dstIndex = tileY * width + tileX;
for (int y = -ATLAS_BORDER; y < h + ATLAS_BORDER; y++) {
for (int x = -ATLAS_BORDER; x < w + ATLAS_BORDER; x++) {
TR::Color32 *p = &src[mm.y * stride + mm.x];
p += clamp(x, 0, w - 1);
p += clamp(y, 0, h - 1) * stride;
dst[dstIndex++] = *p;
}
dstIndex += width - ATLAS_BORDER * 2 - w;
}
cx += tileX + ATLAS_BORDER;
cy += tileY + ATLAS_BORDER;
}
for (int i = 0; i < uvCount; i++) {
if (uv[i].x == mm.z) uv[i].x++;
if (uv[i].y == mm.w) uv[i].y++;
uv[i].x += cx;
uv[i].y += cy;
uv[i].x = int32(uv[i].x) * 32767 / width;
uv[i].y = int32(uv[i].y) * 32767 / height;
}
// apply ref for instanced tile
if (data) return;
int ref = tileX;
if (ref < level->objectTexturesCount) { // textures
mm = level->objectTextures[ref].getMinMax();
} else {
ref -= level->objectTexturesCount;
if (ref < level->spriteTexturesCount) // sprites
mm = level->spriteTextures[ref].getMinMax();
else
ASSERT(false); // only object textures and sprites may be instanced
}
for (int i = 0; i < uvCount; i++) {
uv[i].x += mm.x;
uv[i].y += mm.y;
}
}
void initTextures() {
@@ -359,62 +587,56 @@ struct Level : IGame {
return;
}
// merge all tiles into one 1024x1024 32bpp
TR::Color32 *data = new TR::Color32[1024 * 1024];
for (int i = 0; i < level.tilesCount; i++) {
int tx = (i % 4) * 256;
int ty = (i / 4) * 256;
// repack texture tiles
Atlas *tiles = new Atlas(level.objectTexturesCount + level.spriteTexturesCount + 4, &level, fillCallback);
// add textures
int texIdx = level.version == TR::VER_TR1_PSX ? 256 : 0; // skip palette color for PSX version
for (int i = texIdx; i < level.objectTexturesCount; i++) {
TR::ObjectTexture &t = level.objectTextures[i];
int16 tx = (t.tile.index % 4) * 256;
int16 ty = (t.tile.index / 4) * 256;
TR::Color32 *ptr = &data[ty * 1024 + tx];
for (int y = 0; y < 256; y++) {
memcpy(ptr, &level.tiles[i].color[y * 256], 256 * sizeof(TR::Color32));
ptr += 1024;
}
short4 uv;
uv.x = tx + min(min(t.texCoord[0].x, t.texCoord[1].x), t.texCoord[2].x);
uv.y = ty + min(min(t.texCoord[0].y, t.texCoord[1].y), t.texCoord[2].y);
uv.z = tx + max(max(t.texCoord[0].x, t.texCoord[1].x), t.texCoord[2].x) + 1;
uv.w = ty + max(max(t.texCoord[0].y, t.texCoord[1].y), t.texCoord[2].y) + 1;
tiles->add(uv, texIdx++);
}
// add sprites
for (int i = 0; i < level.spriteTexturesCount; i++) {
TR::SpriteTexture &t = level.spriteTextures[i];
int16 tx = (t.tile % 4) * 256;
int16 ty = (t.tile / 4) * 256;
// white texture
for (int y = 1020; y < 1024; y++)
for (int x = 1020; x < 1024; x++) {
int i = y * 1024 + x;
data[i].r = data[i].g = data[i].b = data[i].a = 255; // white texel for colored triangles
}
short4 uv;
uv.x = tx + t.texCoord[0].x;
uv.y = ty + t.texCoord[0].y;
uv.z = tx + t.texCoord[1].x + 1;
uv.w = ty + t.texCoord[1].y + 1;
// uint32 healthBar[1+5+1] = { 0xFF2C5C70, 0xFF2C5C70, 0xFF4878A4, 0xFF2C5C70, 0xFF004458, 0xFF143050, 0xFF143050 };
uint32 healthBar[1+5+1] = { 0xFF2C5D71, 0xFF2C5D71, 0xFF5E81AE, 0xFF2C5D71, 0xFF1B4557, 0xFF16304F, 0xFF16304F };
tiles->add(uv, texIdx++);
}
// add health bar
tiles->add(short4(2048, 2048, 2048, 2048 + 4), texIdx++);
// add oxygen bar
tiles->add(short4(4096, 4096, 4096, 4096 + 4), texIdx++);
// add option bar
tiles->add(short4(8192, 8192, 8192 + 4, 8192 + 4), texIdx++);
// add white color
tiles->add(short4(2048, 2048, 2048, 2048), texIdx++);
// get result texture
atlas = tiles->pack();
for (int y = 0; y < COUNT(healthBar); y++)
for (int x = 0; x < 2; x++) {
int i = (TEX_HEALTH_BAR_Y + y) * 1024 + (TEX_HEALTH_BAR_X + x);
*((uint32*)&data[i]) = healthBar[y];
}
delete tiles;
uint32 oxygenBar[1+5+1] = { 0xFF647464, 0xFF647464, 0xFFA47848, 0xFF647464, 0xFF4C504C, 0xFF303030, 0xFF303030 };
for (int y = 0; y < COUNT(oxygenBar); y++)
for (int x = 0; x < 2; x++) {
int i = (TEX_OXYGEN_BAR_Y + y) * 1024 + (TEX_OXYGEN_BAR_X + x);
*((uint32*)&data[i]) = oxygenBar[y];
}
/*
FILE *f = fopen("atlas.raw", "wb");
fwrite(data, 1024 * 1024 * 4, 1, f);
fclose(f);
*/
/*
memset(data, 255, 4 * 1024 * 1024);
for (int i = 0; i < 1024; i++)
for (int j = 0; j < 1024; j++)
data[i * 1024 + j].b = data[i * 1024 + j].g = ((i % 8 == 0) || (j % 8 == 0)) ? 0 : 255;
*/
atlas = new Texture(1024, 1024, Texture::RGBA, false, data, true, false); // TODO: generate mips
LOG("atlas: %d x %d\n", atlas->width, atlas->height);
PROFILE_LABEL(TEXTURE, atlas->ID, "atlas");
uint32 whitePix = 0xFFFFFFFF;
cube = new Texture(1, 1, Texture::RGBA, true, &whitePix);
delete[] data;
delete[] level.tiles;
level.tiles = NULL;
}
@@ -584,7 +806,7 @@ struct Level : IGame {
void renderEntity(const TR::Entity &entity) {
//if (entity.room != lara->getRoomIndex()) return;
if (entity.type == TR::Entity::NONE || !entity.modelIndex) return;
if (Core::pass == Core::passShadow && !TR::castShadow(entity.type)) return;
if (Core::pass == Core::passShadow && !entity.castShadow()) return;
ASSERT(entity.controller);
@@ -592,8 +814,10 @@ struct Level : IGame {
Controller *controller = (Controller*)entity.controller;
TR::Room &room = level.rooms[entity.room];
if (entity.type != TR::Entity::LARA) // TODO: remove this hack (collect conjugate room entities)
int roomIndex = controller->getRoomIndex();
TR::Room &room = level.rooms[roomIndex];
if (!entity.isLara() && !entity.isActor())
if (!room.flags.visible || entity.flags.invisible || entity.flags.rendered)
return;
@@ -603,16 +827,18 @@ struct Level : IGame {
if (entity.type == TR::Entity::CRYSTAL)
type = Shader::MIRROR;
int roomIndex = entity.room;
setRoomParams(roomIndex, type, 1.0f, intensityf(lum), controller->specular, 1.0f, isModel ? !mesh->models[entity.modelIndex - 1].opaque : true);
if (type == Shader::SPRITE) {
float alpha = (entity.type == TR::Entity::SMOKE || entity.type == TR::Entity::WATER_SPLASH) ? 0.75f : 1.0f;
setRoomParams(roomIndex, type, 0.5f, intensityf(lum), controller->specular, alpha, isModel ? !mesh->models[entity.modelIndex - 1].opaque : true);
} else
setRoomParams(roomIndex, type, 1.0f, intensityf(lum), controller->specular, 1.0f, isModel ? !mesh->models[entity.modelIndex - 1].opaque : true);
if (isModel) { // model
vec3 pos = controller->getPos();
if (Core::settings.detail.ambient) {
AmbientCache::Cube cube;
if (Core::stats.frame != controller->frameIndex) {
ambientCache->getAmbient(entity.room, pos, cube);
ambientCache->getAmbient(roomIndex, pos, cube);
if (cube.status == AmbientCache::Cube::READY)
memcpy(controller->ambient, cube.colors, sizeof(cube.colors)); // store last calculated ambient into controller
}
@@ -632,46 +858,73 @@ struct Level : IGame {
}
void update() {
#ifdef LEVEL_EDITOR
if (Input::down[ik0]) {
Input::down[ik0] = false;
lara->reset(TR::NO_ROOM, camera->pos, camera->angle.y, false);
}
#endif
if (isTitle()) {
sndCurrent->volume = 0.0f;
if (isCutscene() && !sndSoundtrack)
return;
if (Input::state[cInventory] && level.id != TR::TITLE)
inventory.toggle();
Sound::Sample *sndChanged = sndCurrent;
if (inventory.isActive() || level.id == TR::TITLE) {
Sound::reverb.setRoomSize(vec3(1.0f));
inventory.update();
return;
}
if (level.id != TR::TITLE)
sndChanged = NULL;
} else {
params->time += Core::deltaTime;
params->time += Core::deltaTime;
updateEffect();
for (int i = 0; i < level.entitiesCount; i++) {
TR::Entity &e = level.entities[i];
if (e.type != TR::Entity::NONE) {
Controller *controller = (Controller*)e.controller;
if (controller) {
controller->update();
if (waterCache && e.type == TR::Entity::WATERFALL && ((Waterfall*)controller)->drop) { // add water drops for waterfalls
Waterfall *w = (Waterfall*)controller;
waterCache->addDrop(w->dropPos, w->dropRadius, w->dropStrength);
}
}
Controller *c = Controller::first;
while (c) {
Controller *next = c->next;
c->update();
c = next;
}
if (!isCutscene() && camera->state != Camera::STATE_STATIC)
camera->state = lara->emptyHands() ? Camera::STATE_FOLLOW : Camera::STATE_COMBAT;
camera->update();
if (waterCache)
waterCache->update();
Controller::clearInactive();
sndChanged = camera->isUnderwater() ? sndUnderwater : sndSoundtrack;
}
camera->update();
if (sndChanged != sndCurrent) {
if (sndCurrent) sndCurrent->setVolume(0.0f, 0.2f);
if (sndChanged) sndChanged->setVolume(1.0f, 0.2f);
sndCurrent = sndChanged;
}
}
sndCurrent = camera->isUnderwater() ? sndUnderwater : sndSoundtrack;
void updateEffect() {
if (effect == TR::Effect::NONE)
return;
if (sndSoundtrack && sndCurrent != sndSoundtrack) sndSoundtrack->volume = 0.0f;
if (sndUnderwater && sndCurrent != sndUnderwater) sndUnderwater->volume = 0.0f;
if (sndCurrent) sndCurrent->volume = 1.0f;
effectTimer += Core::deltaTime;
if (waterCache)
waterCache->update();
switch (effect) {
case TR::Effect::FLICKER : {
int idx = flickerIdx;
switch (flickerIdx) {
case 0 : if (effectTimer > 3.0f) flickerIdx++; break;
case 1 : if (effectTimer > 3.1f) flickerIdx++; break;
case 2 : if (effectTimer > 3.5f) flickerIdx++; break;
case 3 : if (effectTimer > 3.6f) flickerIdx++; break;
case 4 : if (effectTimer > 4.1f) { flickerIdx++; effect = TR::Effect::NONE; } break;
}
if (idx != flickerIdx)
level.isFlipped = !level.isFlipped;
break;
}
default : return;
}
}
void setup() {
@@ -689,7 +942,7 @@ struct Level : IGame {
for (int i = 0; i < level.entitiesCount; i++) {
TR::Entity &entity = level.entities[i];
if (entity.flags.rendered)
if (entity.controller && entity.flags.rendered)
((Controller*)entity.controller)->renderShadow(mesh);
}
}
@@ -769,17 +1022,13 @@ struct Level : IGame {
}
void getVisibleRooms(int *roomsList, int &roomsCount, int from, int to, const vec4 &viewPort, bool water, int count = 0) {
if (camera->cutscene) {
roomsCount = level.roomsCount;
for (int i = 0; i < roomsCount; i++)
roomsList[i] = i;
if (count > 16) {
//ASSERT(false);
return;
}
if (count > 16) {
ASSERT(false);
return;
}
if (level.rooms[to].alternateRoom > -1 && level.isFlipped)
to = level.rooms[to].alternateRoom;
TR::Room &room = level.rooms[to];
@@ -795,10 +1044,8 @@ struct Level : IGame {
for (int i = 0; i < room.portalsCount; i++) {
TR::Room::Portal &p = room.portals[i];
if (from == room.portals[i].roomIndex || !checkPortal(room, p, viewPort, clipPort))
continue;
getVisibleRooms(roomsList, roomsCount, to, p.roomIndex, clipPort, water, count + 1);
if (from != room.portals[i].roomIndex && checkPortal(room, p, viewPort, clipPort))
getVisibleRooms(roomsList, roomsCount, to, p.roomIndex, clipPort, water, count + 1);
}
}
@@ -847,6 +1094,7 @@ struct Level : IGame {
if (water && waterCache && waterCache->visible) {
waterCache->renderMask();
waterCache->getRefract();
setMainLight(lara);
waterCache->render();
}
}
@@ -906,11 +1154,19 @@ struct Level : IGame {
#ifdef _DEBUG
void renderDebug() {
if (level.id == TR::TITLE) return;
// Core::mViewInv = camera->mViewInv;
// Core::mView = Core::mViewInv.inverse();
Core::setViewport(0, 0, Core::width, Core::height);
camera->setup(true);
if (Input::down[ikF]) {
level.isFlipped = !level.isFlipped;
Input::down[ikF] = false;
}
/*
static int snd_index = 0;
if (Input::down[ikG]) {
snd_index = (snd_index + 1) % level.soundsInfoCount;
@@ -918,7 +1174,7 @@ struct Level : IGame {
lara->playSound(snd_index, lara->pos, 0);
Input::down[ikG] = false;
}
/*
static int modelIndex = 0;
static bool lastStateK = false;
static int lastEntity = -1;
@@ -1041,9 +1297,88 @@ struct Level : IGame {
// Debug::Level::blocks(level);
// Debug::Level::path(level, (Enemy*)level.entities[86].controller);
// Debug::Level::debugOverlaps(level, lara->box);
// Core::setBlending(bmNone);
/*
static int dbg_ambient = 0;
dbg_ambient = int(params->time * 2) % 4;
shadow->unbind(sShadow);
cube->unbind(sEnvironment);
glActiveTexture(GL_TEXTURE0);
glEnable(GL_TEXTURE_2D);
glDisable(GL_CULL_FACE);
glColor3f(1, 1, 1);
for (int j = 0; j < 6; j++) {
glPushMatrix();
glTranslatef(lara->pos.x, lara->pos.y - 1024, lara->pos.z);
switch (j) {
case 0 : glRotatef( 90, 0, 1, 0); break;
case 1 : glRotatef(-90, 0, 1, 0); break;
case 2 : glRotatef(-90, 1, 0, 0); break;
case 3 : glRotatef( 90, 1, 0, 0); break;
case 4 : glRotatef( 0, 0, 1, 0); break;
case 5 : glRotatef(180, 0, 1, 0); break;
}
glTranslatef(0, 0, 256);
ambientCache->textures[j * 4 + dbg_ambient]->bind(sDiffuse);
glBegin(GL_QUADS);
glTexCoord2f(0, 0); glVertex3f(-256, 256, 0);
glTexCoord2f(1, 0); glVertex3f( 256, 256, 0);
glTexCoord2f(1, 1); glVertex3f( 256, -256, 0);
glTexCoord2f(0, 1); glVertex3f(-256, -256, 0);
glEnd();
glPopMatrix();
}
glEnable(GL_CULL_FACE);
glDisable(GL_TEXTURE_2D);
glLineWidth(4);
glBegin(GL_LINES);
float S = 64.0f;
for (int i = 0; i < level.roomsCount; i++) {
TR::Room &r = level.rooms[i];
for (int j = 0; j < r.xSectors * r.zSectors; j++) {
TR::Room::Sector &s = r.sectors[j];
vec3 p = vec3(float((j / r.zSectors) * 1024 + 512 + r.info.x),
float(max((s.floor - 2) * 256, (s.floor + s.ceiling) * 256 / 2)),
float((j % r.zSectors) * 1024 + 512 + r.info.z));
AmbientCache::Cube &cube = ambientCache->items[ambientCache->offsets[i] + j];
if (cube.status == AmbientCache::Cube::READY) {
glColor3fv((GLfloat*)&cube.colors[0]);
glVertex3f(p.x + 0, p.y + 0, p.z + 0);
glVertex3f(p.x + S, p.y + 0, p.z + 0);
glColor3fv((GLfloat*)&cube.colors[1]);
glVertex3f(p.x + 0, p.y + 0, p.z + 0);
glVertex3f(p.x - S, p.y + 0, p.z + 0);
glColor3fv((GLfloat*)&cube.colors[2]);
glVertex3f(p.x + 0, p.y + 0, p.z + 0);
glVertex3f(p.x + 0, p.y + S, p.z + 0);
glColor3fv((GLfloat*)&cube.colors[3]);
glVertex3f(p.x + 0, p.y + 0, p.z + 0);
glVertex3f(p.x + 0, p.y - S, p.z + 0);
glColor3fv((GLfloat*)&cube.colors[4]);
glVertex3f(p.x + 0, p.y + 0, p.z + 0);
glVertex3f(p.x + 0, p.y + 0, p.z + S);
glColor3fv((GLfloat*)&cube.colors[5]);
glVertex3f(p.x + 0, p.y + 0, p.z + 0);
glVertex3f(p.x + 0, p.y + 0, p.z - S);
}
}
}
glEnd();
glLineWidth(1);
*/
Debug::Level::info(level, lara->getEntity(), lara->animation);
@@ -1058,7 +1393,7 @@ struct Level : IGame {
params->waterHeight = params->clipHeight;
if (ambientCache)
ambientCache->precessQueue();
ambientCache->processQueue();
if (shadow)
renderShadows(lara->getRoomIndex());
@@ -1075,42 +1410,56 @@ struct Level : IGame {
}
void renderUI() {
if (isCutscene()) return;
UI::begin();
// render health & oxygen bars
vec2 size = vec2(180, 10);
if (level.id != TR::TITLE) {
// render health & oxygen bars
vec2 size = vec2(180, 10);
float health = lara->health / float(LARA_MAX_HEALTH);
float oxygen = lara->oxygen / float(LARA_MAX_OXYGEN);
float health = lara->health / float(LARA_MAX_HEALTH);
float oxygen = lara->oxygen / float(LARA_MAX_OXYGEN);
if ((params->time - int(params->time)) < 0.5f) { // blinking
if (health <= 0.2f) health = 0.0f;
if (oxygen <= 0.2f) oxygen = 0.0f;
}
if (inventory.showHealthBar() || (!inventory.active && (!lara->emptyHands() || lara->damageTime > 0.0f || health <= 0.2f))) {
UI::renderBar(0, vec2(UI::width - 32 - size.x, 32), size, health);
if (!inventory.active && !lara->emptyHands()) { // ammo
int index = inventory.contains(lara->getCurrentWeaponInv());
if (index > -1)
inventory.renderItemCount(inventory.items[index], vec2(UI::width - 32 - size.x, 64), size.x);
if ((params->time - int(params->time)) < 0.5f) { // blinking
if (health <= 0.2f) health = 0.0f;
if (oxygen <= 0.2f) oxygen = 0.0f;
}
}
if (!lara->dozy && (lara->stand == Lara::STAND_ONWATER || lara->stand == Character::STAND_UNDERWATER))
UI::renderBar(1, vec2(32, 32), size, oxygen);
if (inventory.showHealthBar() || (!inventory.active && (!lara->emptyHands() || lara->damageTime > 0.0f || health <= 0.2f))) {
UI::renderBar(UI::BAR_HEALTH, vec2(UI::width - 32 - size.x, 32), size, health);
if (!inventory.active && !lara->emptyHands()) { // ammo
int index = inventory.contains(lara->getCurrentWeaponInv());
if (index > -1)
inventory.renderItemCount(inventory.items[index], vec2(UI::width - 32 - size.x, 64), size.x);
}
}
if (!lara->dozy && (lara->stand == Lara::STAND_ONWATER || lara->stand == Character::STAND_UNDERWATER))
UI::renderBar(UI::BAR_OXYGEN, vec2(32, 32), size, oxygen);
}
inventory.renderUI();
UI::renderHelp();
if (level.id != TR::TITLE)
UI::renderHelp();
UI::end();
}
void render() {
bool title = isTitle();
bool title = inventory.isActive() || level.id == TR::TITLE;
bool copyBg = title && lastTitle != title;
lastTitle = title;
if (isEnded) {
Core::setTarget(NULL, true);
UI::begin();
UI::textOut(vec2(0, 480 - 16), STR_LOADING, UI::aCenter, UI::width);
UI::end();
return;
}
if (copyBg) {
Core::defaultTarget = inventory.background[0];
@@ -1127,8 +1476,6 @@ struct Level : IGame {
renderInventory();
renderUI();
lastTitle = title;
}
};

View File

@@ -4,11 +4,8 @@
#include "core.h"
#include "format.h"
#define TEX_HEALTH_BAR_X 1000
#define TEX_HEALTH_BAR_Y 1000
#define TEX_OXYGEN_BAR_X 1002
#define TEX_OXYGEN_BAR_Y 1000
TR::ObjectTexture whiteTile, barTile[3];
struct MeshRange {
int iStart;
@@ -16,7 +13,7 @@ struct MeshRange {
int vStart;
int aIndex;
MeshRange() : aIndex(-1) {}
MeshRange() : iStart(0), iCount(0), vStart(0), aIndex(-1) {}
void setup() const {
glEnableVertexAttribArray(aCoord);
@@ -179,7 +176,6 @@ struct MeshBuilder {
int animTexOffsetsCount;
TR::Level *level;
TR::ObjectTexture whiteTile;
MeshBuilder(TR::Level &level) : level(&level) {
dynMesh = new Mesh(NULL, DYN_MESH_QUADS * 6, NULL, DYN_MESH_QUADS * 4, 1);
@@ -189,15 +185,6 @@ struct MeshBuilder {
initAnimTextures(level);
// create dummy white object textures for non-textured (colored) geometry
whiteTile.attribute = 0;
whiteTile.tile.index = 15;
whiteTile.tile.triangle = 0;
whiteTile.texCoord[0] =
whiteTile.texCoord[1] =
whiteTile.texCoord[2] =
whiteTile.texCoord[3] = { 253, 253 };
// allocate room geometry ranges
rooms = new RoomRange[level.roomsCount];
@@ -224,14 +211,8 @@ struct MeshBuilder {
vCount += mesh.rCount * 4 + mesh.tCount * 3;
}
RoomRange &range = rooms[i];
range.sprites.vStart = vCount;
range.sprites.iStart = iCount;
iCount += d.sCount * 6;
vCount += d.sCount * 4;
range.sprites.iCount = iCount - range.sprites.iStart;
ASSERT(vCount - range.sprites.vStart < 0xFFFF);
}
// get models info
@@ -251,39 +232,24 @@ struct MeshBuilder {
// get size of mesh for sprite sequences
sequences = new MeshRange[level.spriteSequencesCount];
for (int i = 0; i < level.spriteSequencesCount; i++) {
sequences[i].vStart = vCount;
sequences[i].iStart = iCount;
sequences[i].iCount = level.spriteSequences[i].sCount * 6;
iCount += level.spriteSequences[i].sCount * 6;
vCount += level.spriteSequences[i].sCount * 4;
}
// shadow blob mesh (8 triangles, 8 vertices)
shadowBlob.vStart = vCount;
shadowBlob.iStart = iCount;
shadowBlob.iCount = 8 * 3 * 3;
iCount += shadowBlob.iCount;
iCount += 8 * 3 * 3;
vCount += 8 * 2 + 1;
// quad (post effect filter)
quad.vStart = vCount;
quad.iStart = iCount;
quad.iCount = 2 * 3;
iCount += quad.iCount;
iCount += 2 * 3;
vCount += 4;
// circle
circle.vStart = vCount;
circle.iStart = iCount;
circle.iCount = CIRCLE_SEGS * 3;
iCount += circle.iCount;
iCount += CIRCLE_SEGS * 3;
vCount += CIRCLE_SEGS + 1;
// detailed plane
plane.vStart = vCount;
plane.iStart = iCount;
plane.iCount = PLANE_DETAIL * 2 * PLANE_DETAIL * 2 * (2 * 3);
iCount += plane.iCount;
iCount += PLANE_DETAIL * 2 * PLANE_DETAIL * 2 * (2 * 3);
vCount += (PLANE_DETAIL * 2 + 1) * (PLANE_DETAIL * 2 + 1);
// make meshes buffer (single vertex buffer object for all geometry & sprites on level)
@@ -293,20 +259,20 @@ struct MeshBuilder {
int aCount = 0;
// build rooms
int vStartRoom = vCount;
aCount++;
for (int i = 0; i < level.roomsCount; i++) {
TR::Room &room = level.rooms[i];
TR::Room::Data &d = room.data;
RoomRange &range = rooms[i];
int vStart;
for (int transp = 0; transp < 2; transp++) { // opaque, opacity
range.geometry[transp].vStart = vCount;
range.geometry[transp].vStart = vStartRoom;
range.geometry[transp].iStart = iCount;
vStart = vCount;
// rooms geometry
buildRoom(!transp, room, level, indices, vertices, iCount, vCount, vStart);
buildRoom(!transp, room, level, indices, vertices, iCount, vCount, vStartRoom);
// static meshes
for (int j = 0; j < room.meshesCount; j++) {
@@ -319,44 +285,44 @@ struct MeshBuilder {
int y = m.y;
int z = m.z - room.info.z;
int d = m.rotation.value / 0x4000;
buildMesh(!transp, mesh, level, indices, vertices, iCount, vCount, vStart, 0, x, y, z, d);
buildMesh(!transp, mesh, level, indices, vertices, iCount, vCount, vStartRoom, 0, x, y, z, d);
}
range.geometry[transp].iCount = iCount - range.geometry[transp].iStart;
if (range.geometry[transp].iCount)
aCount++;
}
// rooms sprites
vStart = vCount;
range.sprites.vStart = vStartRoom;
range.sprites.iStart = iCount;
for (int j = 0; j < d.sCount; j++) {
TR::Room::Data::Sprite &f = d.sprites[j];
TR::Room::Data::Vertex &v = d.vertices[f.vertex];
TR::SpriteTexture &sprite = level.spriteTextures[f.texture];
addSprite(indices, vertices, iCount, vCount, vStart, v.vertex.x, v.vertex.y, v.vertex.z, sprite, intensity(v.lighting));
addSprite(indices, vertices, iCount, vCount, vStartRoom, v.vertex.x, v.vertex.y, v.vertex.z, sprite, intensity(v.lighting));
}
if (d.sCount) aCount++;
range.sprites.iCount = iCount - range.sprites.iStart;
}
// build models geometry
int vStartModel = vCount;
aCount++;
for (int i = 0; i < level.modelsCount; i++) {
TR::Model &model = level.models[i];
ModelRange &range = models[i];
int vStart = vCount;
range.geometry.vStart = vStart;
range.geometry.vStart = vStartModel;
range.geometry.iStart = iCount;
range.opaque = true;
for (int j = 0; j < model.mCount; j++) {
int index = level.meshOffsets[model.mStart + j];
if (!index && model.mStart + j > 0) continue;
aCount++;
TR::Mesh &mesh = level.meshes[index];
bool opaque = buildMesh(true, mesh, level, indices, vertices, iCount, vCount, vStart, j, 0, 0, 0, 0);
bool opaque = buildMesh(true, mesh, level, indices, vertices, iCount, vCount, vStartModel, j, 0, 0, 0, 0);
if (!opaque)
buildMesh(false, mesh, level, indices, vertices, iCount, vCount, vStart, j, 0, 0, 0, 0);
buildMesh(false, mesh, level, indices, vertices, iCount, vCount, vStartModel, j, 0, 0, 0, 0);
TR::Entity::fixOpaque(model.type, opaque);
range.opaque &= opaque;
}
@@ -364,18 +330,31 @@ struct MeshBuilder {
}
// build sprite sequences
for (int i = 0; i < level.spriteSequencesCount; i++)
int vStartSprite = vCount;
aCount++;
for (int i = 0; i < level.spriteSequencesCount; i++) {
MeshRange &range = sequences[i];
range.vStart = vStartSprite;
range.iStart = iCount;
for (int j = 0; j < level.spriteSequences[i].sCount; j++) {
TR::SpriteTexture &sprite = level.spriteTextures[level.spriteSequences[i].sStart + j];
addSprite(indices, vertices, iCount, vCount, sequences[i].vStart, 0, 0, 0, sprite, 255);
addSprite(indices, vertices, iCount, vCount, vStartSprite, 0, 0, 0, sprite, 255);
}
aCount += level.spriteSequencesCount;
range.iCount = iCount - range.iStart;
}
// build shadow blob
// build common primitives
int vStartCommon = vCount;
aCount++;
shadowBlob.vStart = vStartCommon;
shadowBlob.iStart = iCount;
shadowBlob.iCount = 8 * 3 * 3;
for (int i = 0; i < 9; i++) {
Vertex &v0 = vertices[vCount + i * 2 + 0];
v0.normal = { 0, -1, 0, 0 };
v0.texCoord = { 32688, 32688, 32767, 32767 };
v0.texCoord = { whiteTile.texCoord[0].x, whiteTile.texCoord[0].y, 32767, 32767 };
v0.param = { 0, 0, 0, 0 };
v0.color = { 0, 0, 0, 0 };
@@ -414,10 +393,13 @@ struct MeshBuilder {
}
vCount += 8 * 2 + 1;
iCount += shadowBlob.iCount;
aCount++;
// quad
addQuad(indices, iCount, vCount, quad.vStart, vertices, &whiteTile);
quad.vStart = vStartCommon;
quad.iStart = iCount;
quad.iCount = 2 * 3;
addQuad(indices, iCount, vCount, vStartCommon, vertices, &whiteTile);
vertices[vCount + 3].coord = { -1, -1, 0, 0 };
vertices[vCount + 2].coord = { 1, -1, 1, 0 };
vertices[vCount + 1].coord = { 1, 1, 1, 1 };
@@ -427,41 +409,48 @@ struct MeshBuilder {
Vertex &v = vertices[vCount + i];
v.normal = { 0, 0, 0, 0 };
v.color = { 255, 255, 255, 255 };
v.texCoord = { 32688, 32688, 32767, 32767 };
v.texCoord = { whiteTile.texCoord[0].x, whiteTile.texCoord[0].y, 32767, 32767 };
v.param = { 0, 0, 0, 0 };
}
vCount += 4;
aCount++;
// circle
circle.vStart = vStartCommon;
circle.iStart = iCount;
circle.iCount = CIRCLE_SEGS * 3;
vec2 pos(32767.0f, 0.0f);
vec2 cs(cosf(PI2 / CIRCLE_SEGS), sinf(PI2 / CIRCLE_SEGS));
int baseIdx = vCount - vStartCommon;
for (int i = 0; i < CIRCLE_SEGS; i++) {
Vertex &v = vertices[vCount + i];
pos.rotate(cs);
v.coord = { short(pos.x), short(pos.y), 0, 0 };
v.normal = { 0, 0, 0, 0 };
v.color = { 255, 255, 255, 255 };
v.texCoord = { 32688, 32688, 32767, 32767 };
v.texCoord = { whiteTile.texCoord[0].x, whiteTile.texCoord[0].y, 32767, 32767 };
v.param = { 0, 0, 0, 0 };
indices[iCount++] = i;
indices[iCount++] = (i + 1) % CIRCLE_SEGS;
indices[iCount++] = CIRCLE_SEGS;
indices[iCount++] = baseIdx + i;
indices[iCount++] = baseIdx + (i + 1) % CIRCLE_SEGS;
indices[iCount++] = baseIdx + CIRCLE_SEGS;
}
vertices[vCount + CIRCLE_SEGS] = vertices[vCount];
vertices[vCount + CIRCLE_SEGS].coord = { 0, 0, 0, 0 };
vCount += CIRCLE_SEGS + 1;
aCount++;
// plane
plane.vStart = vStartCommon;
plane.iStart = iCount;
plane.iCount = PLANE_DETAIL * 2 * PLANE_DETAIL * 2 * (2 * 3);
baseIdx = vCount - vStartCommon;
for (int16 j = -PLANE_DETAIL; j <= PLANE_DETAIL; j++)
for (int16 i = -PLANE_DETAIL; i <= PLANE_DETAIL; i++) {
vertices[vCount++].coord = { i, j, 0, 0 };
if (j < PLANE_DETAIL && i < PLANE_DETAIL) {
int idx = (j + PLANE_DETAIL) * (PLANE_DETAIL * 2 + 1) + i + PLANE_DETAIL;
int idx = baseIdx + (j + PLANE_DETAIL) * (PLANE_DETAIL * 2 + 1) + i + PLANE_DETAIL;
indices[iCount + 0] = idx + PLANE_DETAIL * 2 + 1;
indices[iCount + 1] = idx + 1;
indices[iCount + 2] = idx;
@@ -471,7 +460,7 @@ struct MeshBuilder {
iCount += 6;
}
}
aCount++;
LOG("MegaMesh: %d %d %d\n", iCount, vCount, aCount);
// compile buffer and ranges
@@ -483,24 +472,35 @@ struct MeshBuilder {
PROFILE_LABEL(BUFFER, mesh->ID[1], "Geometry vertices");
// initialize Vertex Arrays
MeshRange rangeRoom;
rangeRoom.vStart = vStartRoom;
mesh->initRange(rangeRoom);
for (int i = 0; i < level.roomsCount; i++) {
RoomRange &r = rooms[i];
if (r.geometry[0].iCount)
mesh->initRange(r.geometry[0]);
if (r.geometry[1].iCount)
mesh->initRange(r.geometry[1]);
if (r.sprites.iCount)
mesh->initRange(r.sprites);
r.geometry[0].aIndex = rangeRoom.aIndex;
r.geometry[1].aIndex = rangeRoom.aIndex;
r.sprites.aIndex = rangeRoom.aIndex;
}
for (int i = 0; i < level.spriteSequencesCount; i++)
mesh->initRange(sequences[i]);
MeshRange rangeModel;
rangeModel.vStart = vStartModel;
mesh->initRange(rangeModel);
for (int i = 0; i < level.modelsCount; i++)
mesh->initRange(models[i].geometry);
mesh->initRange(shadowBlob);
mesh->initRange(quad);
mesh->initRange(circle);
mesh->initRange(plane);
models[i].geometry.aIndex = rangeModel.aIndex;
MeshRange rangeSprite;
rangeSprite.vStart = vStartSprite;
mesh->initRange(rangeSprite);
for (int i = 0; i < level.spriteSequencesCount; i++)
sequences[i].aIndex = rangeSprite.aIndex;
MeshRange rangeCommon;
rangeCommon.vStart = vStartCommon;
mesh->initRange(rangeCommon);
shadowBlob.aIndex = rangeCommon.aIndex;
quad.aIndex = rangeCommon.aIndex;
circle.aIndex = rangeCommon.aIndex;
plane.aIndex = rangeCommon.aIndex;
}
~MeshBuilder() {
@@ -535,16 +535,18 @@ struct MeshBuilder {
return res;
}
bool roomCheckWaterPortal(TR::Room room) {
for (int i = 0; i < room.portalsCount; i++)
if (room.flags.water ^ level->rooms[room.portals[i].roomIndex].flags.water)
bool isWaterSurface(int delta, int roomIndex, bool fromWater) {
if (roomIndex != TR::NO_ROOM && delta == 0) {
TR::Room &r = level->rooms[roomIndex];
if (r.flags.water ^ fromWater)
return true;
if (r.alternateRoom > -1 && level->rooms[r.alternateRoom].flags.water ^ fromWater)
return true;
}
return false;
}
void roomRemoveWaterSurfaces(TR::Room &room, int &iCount, int &vCount) {
if (!roomCheckWaterPortal(room)) return;
// remove animated water polygons from room geometry
for (int i = 0; i < room.data.rCount; i++) {
TR::Rectangle &f = room.data.rectangles[i];
@@ -568,8 +570,8 @@ struct MeshBuilder {
if (yt > 0 && yb > 0) continue;
if ((yt == 0 && s.roomAbove != TR::NO_ROOM && (level->rooms[s.roomAbove].flags.water ^ room.flags.water)) ||
(yb == 0 && s.roomBelow != TR::NO_ROOM && (level->rooms[s.roomBelow].flags.water ^ room.flags.water))) {
if (isWaterSurface(yt, s.roomAbove, room.flags.water) ||
isWaterSurface(yb, s.roomBelow, room.flags.water)) {
f.vertices[0] = 0xFFFF; // mark as unused
iCount -= 6;
vCount -= 4;
@@ -597,8 +599,8 @@ struct MeshBuilder {
if (yt > 0 && yb > 0) continue;
if ((yt <= 1 && s.roomAbove != TR::NO_ROOM && (level->rooms[s.roomAbove].flags.water ^ room.flags.water)) ||
(yb <= 1 && s.roomBelow != TR::NO_ROOM && (level->rooms[s.roomBelow].flags.water ^ room.flags.water))) {
if (isWaterSurface(yt, s.roomAbove, room.flags.water) ||
isWaterSurface(yb, s.roomBelow, room.flags.water)) {
f.vertices[0] = 0xFFFF; // mark as unused
iCount -= 3;
vCount -= 3;
@@ -674,8 +676,7 @@ struct MeshBuilder {
for (int j = 0; j < mesh.rCount; j++) {
TR::Rectangle &f = mesh.rectangles[j];
bool textured = !(f.texture & 0x8000);
TR::ObjectTexture &t = textured ? level.objectTextures[f.texture] : whiteTile;
TR::ObjectTexture &t = f.color ? whiteTile : level.objectTextures[f.texture];
if (t.attribute != 0)
isOpaque = false;
@@ -683,7 +684,7 @@ struct MeshBuilder {
if (opaque != (t.attribute == 0))
continue;
TR::Color24 c = textured ? COLOR_WHITE : level.getColor(f.texture);
TR::Color24 c = f.color ? level.getColor(f.texture) : COLOR_WHITE;
addQuad(indices, iCount, vCount, vStart, vertices, &t,
mesh.vertices[f.vertices[0]].coord,
@@ -704,8 +705,7 @@ struct MeshBuilder {
for (int j = 0; j < mesh.tCount; j++) {
TR::Triangle &f = mesh.triangles[j];
bool textured = !(f.texture & 0x8000);
TR::ObjectTexture &t = textured ? level.objectTextures[f.texture] : whiteTile;
TR::ObjectTexture &t = f.color ? whiteTile : level.objectTextures[f.texture];
if (t.attribute != 0)
isOpaque = false;
@@ -713,7 +713,7 @@ struct MeshBuilder {
if (opaque != (t.attribute == 0))
continue;
TR::Color24 c = textured ? COLOR_WHITE : level.getColor(f.texture);
TR::Color24 c = f.color ? level.getColor(f.texture) : COLOR_WHITE;
addTriangle(indices, iCount, vCount, vStart, vertices, &t);
@@ -732,11 +732,7 @@ struct MeshBuilder {
}
vec2 getTexCoord(const TR::ObjectTexture &tex) {
int tile = tex.tile.index;
int tx = (tile % 4) * 256;
int ty = (tile / 4) * 256;
return vec2( (float)(((tx + tex.texCoord[0].x) << 5) + 16),
(float)(((ty + tex.texCoord[0].y) << 5) + 16) ) * (1.0f / 32767.0f);
return vec2(tex.texCoord[0].x / 32767.0f, tex.texCoord[0].y / 32767.0f);
}
void initAnimTextures(TR::Level &level) {
@@ -800,34 +796,15 @@ struct MeshBuilder {
uint8 range, frame;
tex = getAnimTexture(tex, range, frame);
int tile = tex->tile.index;
int tx = (tile % 4) * 256;
int ty = (tile / 4) * 256;
int count = triangle ? 3 : 4;
for (int i = 0; i < count; i++) {
Vertex &v = vertices[vCount + i];
v.texCoord.x = ((tx + tex->texCoord[i].x) << 5) + 16;
v.texCoord.y = ((ty + tex->texCoord[i].y) << 5) + 16;
v.texCoord.z = 32767;
v.texCoord.w = 32767;
v.param = { range, frame, 0, 0 };
v.texCoord = { tex->texCoord[i].x, tex->texCoord[i].y, 32767, 32767 };
v.param = { range, frame, 0, 0 };
}
if (level->version == TR::Level::VER_TR1_PSX && !triangle)
if (level->version == TR::VER_TR1_PSX && !triangle)
swap(vertices[vCount + 2].texCoord, vertices[vCount + 3].texCoord);
/*
short2 uv[4] = {
{0, 0}, {32767, 0}, {32767, 32767}, {0, 32767}
};
for (int i = 0; i < count; i++) {
Vertex &v = vertices[vCount + i];
v.texCoord.x = uv[i].x;
v.texCoord.y = uv[i].y;
}
*/
}
void addTriangle(Index *indices, int &iCount, int vCount, int vStart, Vertex *vertices, TR::ObjectTexture *tex) {
@@ -921,23 +898,15 @@ struct MeshBuilder {
quad[0].color = quad[1].color = quad[2].color = quad[3].color = { 255, 255, 255, intensity };
quad[0].param = quad[1].param = quad[2].param = quad[3].param = { 0, 0, 0, 0 };
int tx = (sprite.tile % 4) * 256;
int ty = (sprite.tile / 4) * 256;
int16 u0 = (((tx + sprite.texCoord[0].x) << 5));
int16 v0 = (((ty + sprite.texCoord[0].y) << 5));
int16 u1 = (((tx + sprite.texCoord[1].x) << 5));
int16 v1 = (((ty + sprite.texCoord[1].y) << 5));
quad[0].texCoord = { u0, v0, sprite.l, sprite.t };
quad[1].texCoord = { u1, v0, sprite.r, sprite.t };
quad[2].texCoord = { u1, v1, sprite.r, sprite.b };
quad[3].texCoord = { u0, v1, sprite.l, sprite.b };
quad[0].texCoord = { sprite.texCoord[0].x, sprite.texCoord[0].y, sprite.l, sprite.t };
quad[1].texCoord = { sprite.texCoord[1].x, sprite.texCoord[0].y, sprite.r, sprite.t };
quad[2].texCoord = { sprite.texCoord[1].x, sprite.texCoord[1].y, sprite.r, sprite.b };
quad[3].texCoord = { sprite.texCoord[0].x, sprite.texCoord[1].y, sprite.l, sprite.b };
vCount += 4;
}
void addBar(Index *indices, Vertex *vertices, int &iCount, int &vCount, int type, const vec2 &pos, const vec2 &size, uint32 color) {
void addBar(Index *indices, Vertex *vertices, int &iCount, int &vCount, const TR::ObjectTexture &tile, const vec2 &pos, const vec2 &size, uint32 color) {
addQuad(indices, iCount, vCount, 0, vertices, NULL);
int16 minX = int16(pos.x);
@@ -955,22 +924,9 @@ struct MeshBuilder {
v.normal = { 0, 0, 0, 0 };
v.color = *((ubyte4*)&color);
int16 s, t;
short2 uv = tile.texCoord[i];
if (type == 0) { // health bar
s = TEX_HEALTH_BAR_X + 1;
t = TEX_HEALTH_BAR_Y + 1;
} else { // oxygen bar
s = TEX_OXYGEN_BAR_X + 1;
t = TEX_OXYGEN_BAR_Y + 1;
}
if (i > 1) t += 5;
s = int(s) * 32767 / 1024;
t = int(t) * 32767 / 1024;
v.texCoord = { s, t, 32767, 32767 };
v.texCoord = { uv.x, uv.y, 32767, 32767 };
v.param = { 0, 0, 0, 0 };
}
@@ -978,6 +934,8 @@ struct MeshBuilder {
}
void addFrame(Index *indices, Vertex *vertices, int &iCount, int &vCount, const vec2 &pos, const vec2 &size, uint32 color1, uint32 color2) {
short4 uv = { whiteTile.texCoord[0].x, whiteTile.texCoord[0].y, 32767, 32767 };
int16 minX = int16(pos.x);
int16 minY = int16(pos.y);
int16 maxX = int16(size.x) + minX;
@@ -997,7 +955,7 @@ struct MeshBuilder {
Vertex &v = vertices[vCount + i];
v.normal = { 0, 0, 0, 0 };
v.color = *((ubyte4*)&color1);
v.texCoord = { 32688, 32688, 32767, 32767 };
v.texCoord = uv;
}
addQuad(indices, iCount, vCount, 0, vertices, NULL); vCount += 4;
@@ -1017,14 +975,13 @@ struct MeshBuilder {
Vertex &v = vertices[vCount + i];
v.normal = { 0, 0, 0, 0 };
v.color = *((ubyte4*)&color2);
v.texCoord = { 32688, 32688, 32767, 32767 };
v.texCoord = uv;
}
addQuad(indices, iCount, vCount, 0, vertices, NULL); vCount += 4;
addQuad(indices, iCount, vCount, 0, vertices, NULL); vCount += 4;
}
void bind() {
mesh->bind();
}

View File

@@ -10,7 +10,7 @@ android {
versionCode 1
versionName "0.1"
ndk {
abiFilters 'armeabi-v7a','x86','x86_64'
abiFilters 'armeabi-v7a', 'x86', 'x86_64'
}
externalNativeBuild {
cmake {
@@ -29,6 +29,9 @@ android {
path "CMakeLists.txt"
}
}
aaptOptions {
noCompress 'psx', 'phd', 'pcx', 'mp3', 'ogg'
}
}
dependencies {

View File

@@ -9,9 +9,10 @@
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
<!--
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="com.qti.permission.PROFILER" />
android:debuggable="true"
-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"

View File

@@ -10,10 +10,12 @@
JNIEXPORT return_type JNICALL \
Java_org_xproger_openlara_Wrapper_##method_name
time_t startTime;
int getTime() {
timeval time;
gettimeofday(&time, NULL);
return (time.tv_sec * 1000) + (time.tv_usec / 1000);
timeval t;
gettimeofday(&t, NULL);
return int((t.tv_sec - startTime) * 1000 + t.tv_usec / 1000);
}
extern "C" {
@@ -23,25 +25,31 @@ int lastTime;
char Stream::cacheDir[255];
char Stream::contentDir[255];
JNI_METHOD(void, nativeInit)(JNIEnv* env, jobject obj, jstring packName, jstring cacheDir, jint levelOffset, jint musicOffset) {
JNI_METHOD(void, nativeInit)(JNIEnv* env, jobject obj, jstring contentDir, jstring cacheDir, jstring packName, jint levelOffset) {
timeval t;
gettimeofday(&t, NULL);
startTime = t.tv_sec;
const char* str;
Stream::contentDir[0] = Stream::cacheDir[0] = 0;
str = env->GetStringUTFChars(packName, NULL);
Stream *level = new Stream(str);
env->ReleaseStringUTFChars(packName, str);
level->seek(levelOffset);
str = env->GetStringUTFChars(contentDir, NULL);
strcat(Stream::contentDir, str);
env->ReleaseStringUTFChars(contentDir, str);
str = env->GetStringUTFChars(cacheDir, NULL);
strcat(Stream::cacheDir, str);
env->ReleaseStringUTFChars(cacheDir, str);
str = env->GetStringUTFChars(packName, NULL);
Stream *level = new Stream(str);
Stream *music = new Stream(str);
env->ReleaseStringUTFChars(packName, str);
Game::init(level);
level->seek(levelOffset);
music->seek(musicOffset);
Game::init(level, music);
lastTime = getTime();
lastTime = getTime();
}
JNI_METHOD(void, nativeFree)(JNIEnv* env) {

View File

@@ -66,9 +66,8 @@ public class MainActivity extends Activity implements OnTouchListener, OnKeyList
String packName = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_ACTIVITIES).applicationInfo.sourceDir;
// hardcoded demo level and music
AssetFileDescriptor fLevel = this.getResources().openRawResourceFd(R.raw.level2);
AssetFileDescriptor fMusic = this.getResources().openRawResourceFd(R.raw.music);
wrapper.onCreate(packName, getCacheDir().getAbsolutePath() + "/", (int)fLevel.getStartOffset(), (int)fMusic.getStartOffset());
wrapper.onCreate(System.getenv("EXTERNAL_STORAGE") + "/OpenLara/", getCacheDir().getAbsolutePath() + "/", packName, (int)fLevel.getStartOffset());
} catch (Exception e) {
e.printStackTrace();
finish();
@@ -215,9 +214,9 @@ class Sound {
public void run() {
while ( audioTrack.getPlayState() != AudioTrack.PLAYSTATE_STOPPED ) {
if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING && wrapper.ready) {
synchronized (wrapper) {
//synchronized (wrapper) {
Wrapper.nativeSoundFill(buffer);
}
//}
audioTrack.write(buffer, 0, buffer.length);
audioTrack.flush();
} else
@@ -270,7 +269,7 @@ class Touch {
}
class Wrapper implements Renderer {
public static native void nativeInit(String packName, String cacheDir, int levelOffset, int musicOffset);
public static native void nativeInit(String contentDir, String cacheDir, String packName, int levelOffset);
public static native void nativeFree();
public static native void nativeReset();
public static native void nativeResize(int w, int h);
@@ -280,18 +279,18 @@ class Wrapper implements Renderer {
public static native void nativeSoundFill(short buffer[]);
Boolean ready = false;
private String packName;
private String contentDir;
private String cacheDir;
private String packName;
private int levelOffset;
private int musicOffset;
private ArrayList<Touch> touch = new ArrayList<>();
private Sound sound;
void onCreate(String packName, String cacheDir, int levelOffset, int musicOffset) {
this.packName = packName;
this.cacheDir = cacheDir;
void onCreate(String contentDir, String cacheDir, String packName, int levelOffset) {
this.contentDir = contentDir;
this.cacheDir = cacheDir;
this.packName = packName;
this.levelOffset = levelOffset;
this.musicOffset = musicOffset;
sound = new Sound();
sound.start(this);
@@ -325,8 +324,8 @@ class Wrapper implements Renderer {
nativeTouch(t.id, t.state, t.x, t.y);
}
touch.clear();
nativeUpdate();
}
nativeUpdate();
nativeRender();
}
@@ -338,7 +337,7 @@ class Wrapper implements Renderer {
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
if (!ready) {
nativeInit(packName, cacheDir, levelOffset, musicOffset);
nativeInit(contentDir, cacheDir, packName, levelOffset);
sound.play();
ready = true;
}

View File

@@ -11,6 +11,16 @@
#define WND_TITLE "OpenLara"
// Time
unsigned int startTime;
int getTime() {
timeval t;
gettimeofday(&t, NULL);
return int((t.tv_sec - startTime) * 1000 + t.tv_usec / 1000);
}
// Sound
#define SND_FRAME_SIZE 4
#define SND_DATA_SIZE (1024 * SND_FRAME_SIZE)
@@ -70,12 +80,7 @@ void sndFree() {
pthread_mutex_destroy(&sndMutex);
}
int getTime() {
timeval t;
gettimeofday(&t, NULL);
return (t.tv_sec * 1000 + t.tv_usec / 1000);
}
// Input
InputKey keyToInputKey(int code) {
int codes[] = {
113, 114, 111, 116, 65, 23, 36, 9, 50, 37, 64,
@@ -147,7 +152,7 @@ void WndProc(const XEvent &e,Display*dpy,Window wnd) {
char Stream::cacheDir[255];
char Stream::contentDir[255];
int main() {
int main(int argc, char **argv) {
Stream::contentDir[0] = Stream::cacheDir[0] = 0;
const char *home;
@@ -190,8 +195,12 @@ int main() {
Atom WM_DELETE_WINDOW = XInternAtom(dpy, "WM_DELETE_WINDOW", 0);
XSetWMProtocols(dpy, wnd, &WM_DELETE_WINDOW, 1);
timeval t;
gettimeofday(&t, NULL);
startTime = t.tv_sec;
sndInit();
Game::init();
Game::init(argc > 1 ? argv[1] : NULL);
int lastTime = getTime();

View File

@@ -7,10 +7,9 @@
objects = {
/* Begin PBXBuildFile section */
995C97F01E91A857003825B2 /* shaders in Resources */ = {isa = PBXBuildFile; fileRef = 995C97EF1E91A857003825B2 /* shaders */; };
9971B72F1E7C239900F25790 /* LEVEL2.PSX in Resources */ = {isa = PBXBuildFile; fileRef = 9971B72E1E7C239900F25790 /* LEVEL2.PSX */; };
99714F651F621E1000960AA7 /* level in Resources */ = {isa = PBXBuildFile; fileRef = 99714F641F621E1000960AA7 /* level */; };
99714F671F621E5E00960AA7 /* audio in Resources */ = {isa = PBXBuildFile; fileRef = 99714F661F621E5E00960AA7 /* audio */; };
99BFB6A21DCA7F5300E2E997 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 99BFB6A11DCA7F5300E2E997 /* main.cpp */; };
99BFB6A61DCA872D00E2E997 /* 05.ogg in Resources */ = {isa = PBXBuildFile; fileRef = 99BFB6A41DCA872D00E2E997 /* 05.ogg */; };
99BFB6A91DCA87BF00E2E997 /* stb_vorbis.c in Sources */ = {isa = PBXBuildFile; fileRef = 99BFB6A81DCA87BF00E2E997 /* stb_vorbis.c */; };
99BFB6AB1DCA87C500E2E997 /* minimp3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 99BFB6AA1DCA87C500E2E997 /* minimp3.cpp */; };
99C4C0B71796AB730032DE85 /* AGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99C4C0951796A9730032DE85 /* AGL.framework */; };
@@ -21,7 +20,8 @@
/* Begin PBXFileReference section */
995C97EF1E91A857003825B2 /* shaders */ = {isa = PBXFileReference; lastKnownFileType = folder; name = shaders; path = ../../shaders; sourceTree = "<group>"; };
9971B72E1E7C239900F25790 /* LEVEL2.PSX */ = {isa = PBXFileReference; lastKnownFileType = file; name = LEVEL2.PSX; path = ../../../bin/LEVEL2.PSX; sourceTree = SOURCE_ROOT; };
99714F641F621E1000960AA7 /* level */ = {isa = PBXFileReference; lastKnownFileType = folder; name = level; path = ../../../../bin/level; sourceTree = "<group>"; };
99714F661F621E5E00960AA7 /* audio */ = {isa = PBXFileReference; lastKnownFileType = folder; name = audio; path = ../../../../bin/audio; sourceTree = "<group>"; };
99BFB68D1DCA7F1700E2E997 /* lara.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lara.h; path = ../../lara.h; sourceTree = "<group>"; };
99BFB68E1DCA7F1700E2E997 /* format.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = format.h; path = ../../format.h; sourceTree = "<group>"; };
99BFB68F1DCA7F1700E2E997 /* level.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = level.h; path = ../../level.h; sourceTree = "<group>"; };
@@ -40,7 +40,6 @@
99BFB69D1DCA7F1700E2E997 /* texture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = texture.h; path = ../../texture.h; sourceTree = "<group>"; };
99BFB69E1DCA7F1700E2E997 /* libs */ = {isa = PBXFileReference; lastKnownFileType = folder; name = libs; path = ../../libs; sourceTree = "<group>"; };
99BFB6A11DCA7F5300E2E997 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = "<group>"; };
99BFB6A41DCA872D00E2E997 /* 05.ogg */ = {isa = PBXFileReference; lastKnownFileType = file; name = 05.ogg; path = ../../../bin/05.ogg; sourceTree = SOURCE_ROOT; };
99BFB6A81DCA87BF00E2E997 /* stb_vorbis.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = stb_vorbis.c; path = ../../libs/stb_vorbis/stb_vorbis.c; sourceTree = "<group>"; };
99BFB6AA1DCA87C500E2E997 /* minimp3.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = minimp3.cpp; path = ../../libs/minimp3/minimp3.cpp; sourceTree = "<group>"; };
99C4C0931796A96F0032DE85 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; };
@@ -122,8 +121,8 @@
99C4C0A31796AACF0032DE85 /* Supporting Files */ = {
isa = PBXGroup;
children = (
9971B72E1E7C239900F25790 /* LEVEL2.PSX */,
99BFB6A41DCA872D00E2E997 /* 05.ogg */,
99714F661F621E5E00960AA7 /* audio */,
99714F641F621E1000960AA7 /* level */,
99C4C0A41796AACF0032DE85 /* OpenLara-Info.plist */,
);
name = "Supporting Files";
@@ -181,9 +180,8 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
995C97F01E91A857003825B2 /* shaders in Resources */,
99BFB6A61DCA872D00E2E997 /* 05.ogg in Resources */,
9971B72F1E7C239900F25790 /* LEVEL2.PSX in Resources */,
99714F651F621E1000960AA7 /* level in Resources */,
99714F671F621E5E00960AA7 /* audio in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

28
src/platform/rpi/Makefile Normal file
View File

@@ -0,0 +1,28 @@
OBJS=OpenLara.o
BIN=../../../bin/OpenLara.bin
CFLAGS+=-D__RPI__
LDFLAGS+=-L$(SDKSTAGE)/opt/vc/lib/ -lbrcmGLESv2 -lbrcmEGL -lbcm_host -lvcos -lvchiq_arm -lpthread -lrt -lm -L$(SDKSTAGE)/opt/vc/src/hello_pi/libs/vgfont
INCLUDES+=-I$(SDKSTAGE)/opt/vc/include/ -I$(SDKSTAGE)/opt/vc/include/interface/vcos/pthreads -I$(SDKSTAGE)/opt/vc/include/interface/vmcs_host/linux -I./
all: $(BIN) $(LIB)
%.o: %.c
@rm -f $@
$(CC) $(CFLAGS) $(INCLUDES) -g -c $< -o $@ -Wno-deprecated-declarations
%.o: %.cpp
@rm -f $@
$(CXX) $(CFLAGS) $(INCLUDES) -g -c $< -o $@ -Wno-deprecated-declarations
%.bin: $(OBJS)
$(CC) -o $@ -Wl,--whole-archive $(OBJS) $(LDFLAGS) -Wl,--no-whole-archive -rdynamic
%.a: $(OBJS)
$(AR) r $@ $^
clean:
for i in $(OBJS); do (if test -e "$$i"; then ( rm $$i ); fi ); done
@rm -f $(BIN) $(LIB)

3
src/platform/rpi/build.sh Executable file
View File

@@ -0,0 +1,3 @@
set -e
clang++ -std=c++11 -Os -s -g -fno-exceptions -fno-rtti -ffunction-sections -fdata-sections -Wl,--gc-sections -DNDEBUG -D__RPI__ main.cpp ../../libs/stb_vorbis/stb_vorbis.c -I/opt/vc/include -I../../ -o../../../bin/OpenLara -L/opt/vc/lib/ -lbrcmGLESv2 -lbrcmEGL -lX11 -lm -lrt -lpthread -lasound -lbcm_host -ludev
strip ../../../bin/OpenLara --strip-all --remove-section=.comment --remove-section=.note

495
src/platform/rpi/main.cpp Normal file
View File

@@ -0,0 +1,495 @@
#include <string.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <pthread.h>
#include <bcm_host.h>
#include <EGL/egl.h>
#include <fcntl.h>
#include <linux/input.h>
#include <libudev.h>
#include <alsa/asoundlib.h>
#include "game.h"
#define WND_TITLE "OpenLara"
// Time
unsigned int startTime;
int getTime() {
timeval t;
gettimeofday(&t, NULL);
return int((t.tv_sec - startTime) * 1000 + t.tv_usec / 1000);
}
// Sound
snd_pcm_uframes_t SND_FRAMES = 512;
snd_pcm_t *sndOut;
Sound::Frame *sndData;
pthread_t sndThread;
pthread_mutex_t sndMutex;
void* sndFill(void *arg) {
while (sndOut) {
pthread_mutex_lock(&sndMutex);
Sound::fill(sndData, SND_FRAMES);
pthread_mutex_unlock(&sndMutex);
int err = snd_pcm_writei(sndOut, sndData, SND_FRAMES);
if (err < 0) {
LOG("! sound: write %s\n", snd_strerror(err));;
if (err != -EPIPE)
break;
err = snd_pcm_recover(sndOut, err, 0);
if (err < 0) {
LOG("! sound: failed to recover\n");
break;
}
snd_pcm_prepare(sndOut);
}
}
return NULL;
}
bool sndInit() {
unsigned int freq = 44100;
pthread_mutex_init(&sndMutex, NULL);
int err;
if ((err = snd_pcm_open(&sndOut, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
LOG("! sound: open %s\n", snd_strerror(err));\
sndOut = NULL;
return false;
}
snd_pcm_hw_params_t *params;
snd_pcm_hw_params_alloca(&params);
snd_pcm_hw_params_any(sndOut, params);
snd_pcm_hw_params_set_access(sndOut, params, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_channels(sndOut, params, 2);
snd_pcm_hw_params_set_format(sndOut, params, SND_PCM_FORMAT_S16_LE);
snd_pcm_hw_params_set_rate_near(sndOut, params, &freq, NULL);
snd_pcm_hw_params_set_periods(sndOut, params, 4, 0);
snd_pcm_hw_params_set_period_size_near(sndOut, params, &SND_FRAMES, NULL);
snd_pcm_hw_params_get_period_size(params, &SND_FRAMES, 0);
snd_pcm_hw_params(sndOut, params);
snd_pcm_prepare(sndOut);
sndData = new Sound::Frame[SND_FRAMES];
memset(sndData, 0, SND_FRAMES * sizeof(Sound::Frame));
if ((err = snd_pcm_writei(sndOut, sndData, SND_FRAMES)) < 0) {
LOG("! sound: write %s\n", snd_strerror(err));\
sndOut = NULL;
}
snd_pcm_start(sndOut);
pthread_create(&sndThread, NULL, sndFill, NULL);
return true;
}
void sndFree() {
pthread_cancel(sndThread);
pthread_mutex_lock(&sndMutex);
snd_pcm_drop(sndOut);
snd_pcm_drain(sndOut);
snd_pcm_close(sndOut);
pthread_mutex_unlock(&sndMutex);
pthread_mutex_destroy(&sndMutex);
delete[] sndData;
}
// Window
bool wndInit(DISPMANX_DISPLAY_HANDLE_T &display, EGL_DISPMANX_WINDOW_T &window) {
if (graphics_get_display_size(0, (uint32_t*)&window.width, (uint32_t*)&window.height) < 0) {
LOG("! can't get display size\n");
return false;
}
int scale = 1;
window.width /= scale;
window.height /= scale;
VC_RECT_T dstRect, srcRect;
vc_dispmanx_rect_set(&dstRect, 0, 0, window.width, window.height);
vc_dispmanx_rect_set(&srcRect, 0, 0, window.width << 16, window.height << 16);
VC_DISPMANX_ALPHA_T alpha = { DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS, 0xFF, 0 };
display = vc_dispmanx_display_open(0);
DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0);
window.element = vc_dispmanx_element_add(
update, display, 0, &dstRect, 0, &srcRect,
DISPMANX_PROTECTION_NONE, &alpha, NULL, DISPMANX_NO_ROTATE);
vc_dispmanx_update_submit_sync(update);
return true;
}
void wndFree(DISPMANX_DISPLAY_HANDLE_T &display, EGL_DISPMANX_WINDOW_T &window) {
DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0);
vc_dispmanx_element_remove(update, window.element);
vc_dispmanx_update_submit_sync(update);
vc_dispmanx_display_close(display);
}
bool eglInit(EGL_DISPMANX_WINDOW_T &window, EGLDisplay &display, EGLSurface &surface, EGLContext &context) {
static const EGLint eglAttr[] = {
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 24,
EGL_SAMPLES, 0,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_NONE
};
static const EGLint ctxAttr[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (display == EGL_NO_DISPLAY)
return false;
if (eglInitialize(display, NULL, NULL) == EGL_FALSE)
return false;
EGLConfig config;
EGLint configCount;
if (eglChooseConfig(display, eglAttr, &config, 1, &configCount) == EGL_FALSE)
return false;
context = eglCreateContext(display, config, EGL_NO_CONTEXT, ctxAttr);
if (context == EGL_NO_CONTEXT)
return false;
surface = eglCreateWindowSurface(display, config, &window, NULL);
if (surface == EGL_NO_SURFACE)
return false;
if (eglSurfaceAttrib(display, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED) == EGL_FALSE)
return false;
if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
return false;
//eglSwapInterval(display, 0); // turn off vsync
return true;
}
void eglFree(EGLDisplay display, EGLSurface surface, EGLContext context) {
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroySurface(display, surface);
eglDestroyContext(display, context);
eglTerminate(display);
}
// Input
#define MAX_INPUT_DEVICES 16
int inputDevices[MAX_INPUT_DEVICES];
udev *udevObj;
udev_monitor *udevMon;
int udevMon_fd;
vec2 joyL, joyR;
InputKey codeToInputKey(int code) {
switch (code) {
// keyboard
case KEY_LEFT : return ikLeft;
case KEY_RIGHT : return ikRight;
case KEY_UP : return ikUp;
case KEY_DOWN : return ikDown;
case KEY_SPACE : return ikSpace;
case KEY_TAB : return ikTab;
case KEY_ENTER : return ikEnter;
case KEY_ESC : return ikEscape;
case KEY_LEFTSHIFT :
case KEY_RIGHTSHIFT : return ikShift;
case KEY_LEFTCTRL :
case KEY_RIGHTCTRL : return ikCtrl;
case KEY_LEFTALT :
case KEY_RIGHTALT : return ikAlt;
case KEY_0 : return ik0;
case KEY_1 : return ik1;
case KEY_2 : return ik2;
case KEY_3 : return ik3;
case KEY_4 : return ik4;
case KEY_5 : return ik5;
case KEY_6 : return ik6;
case KEY_7 : return ik7;
case KEY_8 : return ik8;
case KEY_9 : return ik9;
case KEY_A : return ikA;
case KEY_B : return ikB;
case KEY_C : return ikC;
case KEY_D : return ikD;
case KEY_E : return ikE;
case KEY_F : return ikF;
case KEY_G : return ikG;
case KEY_H : return ikH;
case KEY_I : return ikI;
case KEY_J : return ikJ;
case KEY_K : return ikK;
case KEY_L : return ikL;
case KEY_M : return ikM;
case KEY_N : return ikN;
case KEY_O : return ikO;
case KEY_P : return ikP;
case KEY_Q : return ikQ;
case KEY_R : return ikR;
case KEY_S : return ikS;
case KEY_T : return ikT;
case KEY_U : return ikU;
case KEY_V : return ikV;
case KEY_W : return ikW;
case KEY_X : return ikX;
case KEY_Y : return ikY;
case KEY_Z : return ikZ;
// mouse
case BTN_LEFT : return ikMouseL;
case BTN_RIGHT : return ikMouseR;
case BTN_MIDDLE : return ikMouseM;
// gamepad
case KEY_HOMEPAGE : return ikEscape;
case BTN_A : return ikJoyA;
case BTN_B : return ikJoyB;
case BTN_X : return ikJoyX;
case BTN_Y : return ikJoyY;
case BTN_TL : return ikJoyLB;
case BTN_TR : return ikJoyRB;
case BTN_SELECT : return ikJoySelect;
case BTN_START : return ikJoyStart;
case BTN_THUMBL : return ikJoyL;
case BTN_THUMBR : return ikJoyR;
case BTN_TL2 : return ikJoyLT;
case BTN_TR2 : return ikJoyRT;
}
return ikNone;
}
int inputDevIndex(const char *node) {
const char *str = strstr(node, "/event");
if (str)
return atoi(str + 6);
return -1;
}
void inputDevAdd(const char *node) {
int index = inputDevIndex(node);
if (index != -1) {
inputDevices[index] = open(node, O_RDONLY | O_NONBLOCK);
ioctl(inputDevices[index], EVIOCGRAB, 1);
//LOG("input: add %s\n", node);
}
}
void inputDevRemove(const char *node) {
int index = inputDevIndex(node);
if (index != -1 && inputDevices[index] != -1) {
close(inputDevices[index]);
//LOG("input: remove %s\n", node);
}
}
bool inputInit() {
joyL = joyR = vec2(0);
for (int i = 0; i < MAX_INPUT_DEVICES; i++)
inputDevices[i] = -1;
udevObj = udev_new();
if (!udevObj)
return false;
udevMon = udev_monitor_new_from_netlink(udevObj, "udev");
udev_monitor_filter_add_match_subsystem_devtype(udevMon, "input", NULL);
udev_monitor_enable_receiving(udevMon);
udevMon_fd = udev_monitor_get_fd(udevMon);
udev_enumerate *e = udev_enumerate_new(udevObj);
udev_enumerate_add_match_subsystem(e, "input");
udev_enumerate_scan_devices(e);
udev_list_entry *devices = udev_enumerate_get_list_entry(e);
udev_list_entry *entry;
udev_list_entry_foreach(entry, devices) {
const char *path, *node;
udev_device *device;
path = udev_list_entry_get_name(entry);
device = udev_device_new_from_syspath(udevObj, path);
node = udev_device_get_devnode(device);
if (node)
inputDevAdd(node);
}
udev_enumerate_unref(e);
return true;
}
void inputFree() {
for (int i = 0; i < MAX_INPUT_DEVICES; i++)
if (inputDevices[i] != -1)
close(inputDevices[i]);
udev_monitor_unref(udevMon);
udev_unref(udevObj);
}
void inputUpdate() {
// get input events
input_event events[16];
for (int i = 0; i < MAX_INPUT_DEVICES; i++) {
if (inputDevices[i] == -1) continue;
int rb = read(inputDevices[i], events, sizeof(events));
input_event *e = events;
while (rb > 0) {
switch (e->type) {
case EV_KEY : {
InputKey key = codeToInputKey(e->code);
if (key == ikMouseL || key == ikMouseR || key == ikMouseM)
Input::setPos(key, Input::mouse.pos);
Input::setDown(key, e->value != 0);
break;
}
case EV_REL : {
vec2 delta(0);
delta[e->code] = float(e->value);
Input::setPos(ikMouseL, Input::mouse.pos + delta);
break;
}
case EV_ABS : {
float v = float(e->value) / 128.0f - 1.0f;
switch (e->code) {
case ABS_X : joyL.x = v; break;
case ABS_Y : joyL.y = v; break;
case ABS_Z : joyR.x = v; break;
case ABS_RZ : joyR.y = v; break;
}
}
}
//LOG("input: type = %d, code = %d, value = %d\n", int(e->type), int(e->code), int(e->value));
e++;
rb -= sizeof(events[0]);
}
}
Input::setPos(ikJoyL, joyL);
Input::setPos(ikJoyR, joyR);
// monitoring plug and unplug input devices
fd_set fds;
FD_ZERO(&fds);
FD_SET(udevMon_fd, &fds);
timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 0;
if (select(udevMon_fd + 1, &fds, NULL, NULL, &tv) > 0 && FD_ISSET(udevMon_fd, &fds)) {
udev_device *device = udev_monitor_receive_device(udevMon);
if (device) {
const char *node = udev_device_get_devnode(device);
if (node) {
const char *action = udev_device_get_action(device);
if (!strcmp(action, "add"))
inputDevAdd(node);
if (!strcmp(action, "remove"))
inputDevRemove(node);
}
udev_device_unref(device);
} else
LOG("! input: receive_device\n");
}
}
char Stream::cacheDir[255];
char Stream::contentDir[255];
int main(int argc, char **argv) {
bcm_host_init();
DISPMANX_DISPLAY_HANDLE_T dmDisplay;
EGL_DISPMANX_WINDOW_T dmWindow;
if (!wndInit(dmDisplay, dmWindow))
return 1;
Core::width = dmWindow.width;
Core::height = dmWindow.height;
EGLDisplay display;
EGLSurface surface;
EGLContext context;
if (!eglInit(dmWindow, display, context, surface)) {
LOG("! can't initialize EGL context\n");
return 1;
}
Stream::contentDir[0] = Stream::cacheDir[0] = 0;
const char *home;
if (!(home = getenv("HOME")))
home = getpwuid(getuid())->pw_dir;
strcat(Stream::cacheDir, home);
strcat(Stream::cacheDir, "/.OpenLara/");
struct stat st = {0};
if (stat(Stream::cacheDir, &st) == -1 && mkdir(Stream::cacheDir, 0777) == -1)
Stream::cacheDir[0] = 0;
timeval t;
gettimeofday(&t, NULL);
startTime = t.tv_sec;
sndInit();
char *lvlName = argc > 1 ? argv[1] : NULL;
char *sndName = argc > 2 ? argv[2] : NULL;
Game::init(lvlName, sndName);
inputInit(); // initialize and grab input devices
int lastTime = getTime();
while (!Input::down[ikEscape]) {
inputUpdate();
int time = getTime();
if (time <= lastTime)
continue;
pthread_mutex_lock(&sndMutex);
Game::update((time - lastTime) * 0.001f);
pthread_mutex_unlock(&sndMutex);
lastTime = time;
Game::render();
eglSwapBuffers(display, surface);
};
sndFree();
Game::free();
inputFree();
eglFree(display, surface, context);
wndFree(dmDisplay, dmWindow);
return 0;
}

View File

View File

@@ -1,9 +1,8 @@
@echo off
cls
set SRC=main.cpp
set SRC=main.cpp ../../libs/stb_vorbis/stb_vorbis.c
set PROJ=OpenLara
set FLAGS=-O3 -Wno-deprecated-register --llvm-opts 2 -fmax-type-align=2 -std=c++11 -Wall -I../../
set PRELOAD=./LEVEL2.PSX
echo.
call em++ %SRC% %FLAGS% -o %PROJ%.js --preload-file %PRELOAD%
call em++ %SRC% %FLAGS% -o %PROJ%.js --preload-file ./level/TITLE.PSX --preload-file ./level/TITLEH.PCX --preload-file ./audio/dummy
gzip.exe -9 -f %PROJ%.data %PROJ%.js %PROJ%.js.mem

View File

@@ -23,7 +23,6 @@
<meta charset="utf-8">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="viewport" content="width=854px, user-scalable=no">
</head>
<body>
<canvas class="game" id="canvas" width="854px" height="480px" oncontextmenu="event.preventDefault()"></canvas><br>
@@ -89,7 +88,7 @@
proc.connect(ctx.destination);
}
var gl = canvasElement.getContext("webgl", {antialias:false}) || canvasElement.getContext("experimental-webgl", {antialias:false});
var gl = canvasElement.getContext("webgl", {antialias:false, premultipliedAlpha: false}) || canvasElement.getContext("experimental-webgl", {antialias:false, premultipliedAlpha: false});
Module.setStatus('Downloading...');
window.onerror = function(event) {
@@ -101,6 +100,16 @@
};
};
</script>
<span id="info">
<input type="file" id="browseFile" style="display:none" accept=".phd,.psx" onchange="readLevel(event)" />
<input type="button" value="Browse Level" onclick="document.getElementById('browseFile').click();" /> (.PHD, .PSX)
<p style="margin:8px">
OpenLara on <a target="_blank" href="https://github.com/XProger/OpenLara">github</a> & <a target="_blank" href="https://www.facebook.com/OpenLaraTR">facebook</a><br>
<br><i>last update: 14.09.2017</i><br>
</p>
</span>
<script>
(function() {
var memoryInitializer = 'OpenLara.js.mem';
@@ -123,30 +132,27 @@
return "Really want to quit the game?";
};
var isMobile = false;
(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) isMobile = true;})(navigator.userAgent||navigator.vendor||window.opera);
if (isMobile) {
canvasElement.className = "game_fs";
statusElement.style.display = 'none';
document.getElementById('info').style.display = 'none';
}
function readLevel(event, home) {
var reader = new FileReader();
reader.onload = function(){
var size = reader.result.byteLength;
var data = Module._malloc(size);
Module.writeArrayToMemory(new Uint8Array(reader.result), data);
Module.ccall('game_level_load', 'null', ['number', 'number', 'number'], [data, size, home]);
Module.ccall('game_level_load', 'null', ['number', 'number'], [data, size]);
};
reader.readAsArrayBuffer(event.target.files[0]);
}
</script>
<audio autoplay loop><source src="05.ogg" type="audio/ogg"></audio>
<span id="info">
<input type="file" id="browseFile" style="display:none" accept=".phd,.psx" onchange="readLevel(event, document.getElementById('isHome').checked)" />
<!-- <label for="browseFile">Browse Level</label> -->
<input type="button" value="Browse Level" onclick="document.getElementById('browseFile').click();" /> (.PHD, .PSX)
<input type="checkbox" id="isHome"><label>alternative model (home suit, gold etc.)</label>
<p style="margin:8px">
OpenLara on <a target="_blank" href="https://github.com/XProger/OpenLara">github</a> & <a target="_blank" href="https://www.facebook.com/OpenLaraTR">facebook</a><br>
</p>
</span>
<script>(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script','//www.google-analytics.com/analytics.js','ga');ga('create', 'UA-60009035-1', 'auto');ga('send', 'pageview');</script>
</body>

View File

View File

@@ -17,8 +17,8 @@ extern "C" {
Sound::fill(frames, count);
}
void EMSCRIPTEN_KEEPALIVE game_level_load(char *data, int size, int home) {
Game::startLevel(new Stream(data, size), NULL, false, home);
void EMSCRIPTEN_KEEPALIVE game_level_load(char *data, int size) {
Game::startLevel(new Stream(data, size));
}
}
@@ -233,6 +233,39 @@ EM_BOOL mouseCallback(int eventType, const EmscriptenMouseEvent *e, void *userDa
return 1;
}
const char *IDB = "db";
void onError(void *str) {
LOG("! IDB error: %s\n", str);
}
void onLoad(void *arg, void *data, int size) {
Stream *stream = (Stream*)arg;
FILE *f = fopen(stream->name, "wb");
fwrite(data, size, 1, f);
fclose(f);
stream->callback(new Stream(stream->name), stream->userData);
delete stream;
}
void onLoadAndStore(void *arg, void *data, int size) {
emscripten_idb_async_store(IDB, ((Stream*)arg)->name, data, size, NULL, NULL, onError);
onLoad(arg, data, size);
}
void onExists(void *arg, int exists) {
if (exists)
emscripten_idb_async_load(IDB, ((Stream*)arg)->name, arg, onLoad, onError);
else
emscripten_async_wget_data(((Stream*)arg)->name, arg, onLoadAndStore, onError);
}
void osDownload(Stream *stream) {
emscripten_idb_async_exists(IDB, stream->name, stream, onExists, onError);
}
char Stream::cacheDir[255];
char Stream::contentDir[255];

View File

@@ -8,15 +8,12 @@ EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
Editor|Win32 = Editor|Win32
Profile|Win32 = Profile|Win32
Release|Win32 = Release|Win32
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6935E070-59B8-418A-9241-70BACB4217B5}.Debug|Win32.ActiveCfg = Debug|Win32
{6935E070-59B8-418A-9241-70BACB4217B5}.Debug|Win32.Build.0 = Debug|Win32
{6935E070-59B8-418A-9241-70BACB4217B5}.Editor|Win32.ActiveCfg = Editor|Win32
{6935E070-59B8-418A-9241-70BACB4217B5}.Editor|Win32.Build.0 = Editor|Win32
{6935E070-59B8-418A-9241-70BACB4217B5}.Profile|Win32.ActiveCfg = Profile|Win32
{6935E070-59B8-418A-9241-70BACB4217B5}.Profile|Win32.Build.0 = Profile|Win32
{6935E070-59B8-418A-9241-70BACB4217B5}.Release|Win32.ActiveCfg = Release|Win32

View File

@@ -1,14 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Editor|Win32'">
<LocalDebuggerWorkingDirectory>../../../bin</LocalDebuggerWorkingDirectory>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
<LocalDebuggerCommandArguments>data/LEVEL3A.PSX</LocalDebuggerCommandArguments>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LocalDebuggerWorkingDirectory>../../../bin</LocalDebuggerWorkingDirectory>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
<LocalDebuggerCommandArguments>data/LEVEL2.PHD</LocalDebuggerCommandArguments>
<LocalDebuggerCommandArguments>
</LocalDebuggerCommandArguments>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile|Win32'">
<LocalDebuggerWorkingDirectory>../../../bin</LocalDebuggerWorkingDirectory>
@@ -17,6 +13,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LocalDebuggerWorkingDirectory>../../../bin</LocalDebuggerWorkingDirectory>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
<LocalDebuggerCommandArguments>data/LEVEL2.PSX</LocalDebuggerCommandArguments>
<LocalDebuggerCommandArguments>
</LocalDebuggerCommandArguments>
</PropertyGroup>
</Project>

View File

@@ -372,8 +372,8 @@ int main(int argc, char** argv) {
joyUpdate();
DWORD time = getTime();
if (time <= lastTime)
continue;
//if (time <= lastTime)
// continue;
EnterCriticalSection(&sndCS);
Game::update((time - lastTime) * 0.001f);

View File

@@ -6,21 +6,23 @@ R"====(
varying vec2 vTexCoord;
uniform int uType;
uniform vec4 uParam;
#ifdef VERTEX
attribute vec4 aCoord;
void main() {
vTexCoord = aCoord.zw;
#ifdef FILTER_DEFAULT
vTexCoord = aCoord.zw * uParam.xy + uParam.zw;
#else
vTexCoord = aCoord.zw;
#endif
gl_Position = vec4(aCoord.xy, 0.0, 1.0);
}
#else
uniform sampler2D sDiffuse;
uniform sampler2D sNormal;
uniform vec4 uParam;
vec4 downsample() { // uParam (textureSize, unused, unused, unused)
float k = 1.0 / uParam.x; // inverted texture size
@@ -29,7 +31,7 @@ uniform int uType;
for (float x = -1.5; x < 2.0; x++) {
vec4 p;
p.xyz = texture2D(sDiffuse, vTexCoord + vec2(x, y) * k).xyz;
p.w = dot(p.xyz, vec3(0.299, 0.587, 0.114));
p.w = dot(p.xyz, vec3(0.299, 0.587, 0.114));
p.xyz *= p.w;
color += p;
}

View File

@@ -18,7 +18,7 @@ varying vec4 vColor;
void main() {
vTexCoord = aTexCoord.xy;
vColor = aColor * uMaterial;
vColor = aColor;
gl_Position = uViewProj * vec4(aCoord.xy * uPosScale.zw + uPosScale.xy, 0.0, 1.0);
}
#else

View File

@@ -14,10 +14,6 @@ varying vec4 vTexCoord; // xy - atlas coords, zw - trapezoidal correction
varying vec2 vCausticsCoord; // - xy caustics texture coord
#endif
//uniform vec4 data[MAX_RANGES + MAX_OFFSETS + 4 + 4 + 1 + 1 + MAX_LIGHTS + MAX_LIGHTS + 1 + 6 + 1 + 32 * 2];
uniform vec2 uAnimTexRanges[MAX_RANGES];
uniform vec2 uAnimTexOffsets[MAX_OFFSETS];
uniform mat4 uLightProj;
uniform mat4 uViewProj;
uniform vec3 uViewPos;
@@ -25,9 +21,8 @@ uniform vec4 uParam; // x - time, y - water height, z - clip plane sign, w - cli
uniform vec3 uLightPos[MAX_LIGHTS];
uniform vec4 uLightColor[MAX_LIGHTS]; // xyz - color, w - radius * intensity
uniform vec4 uRoomSize; // xy - minXZ, zw - maxXZ
uniform vec3 uAmbient[6];
uniform vec4 uMaterial; // x - diffuse, y - ambient, z - specular, w - alpha
uniform vec4 uBasis[32 * 2];
#ifndef PASS_SHADOW
varying vec4 vViewVec; // xyz - dir * dist, w - coord.y
@@ -45,7 +40,21 @@ uniform vec4 uBasis[32 * 2];
#endif
varying vec4 vLight; // lights intensity (MAX_LIGHTS == 4)
#endif
#endif
#ifdef VERTEX
#ifdef TYPE_ENTITY
#if defined(OPT_AMBIENT)
uniform vec3 uAmbient[6];
#endif
uniform vec4 uBasis[32 * 2];
#else
uniform vec4 uBasis[2];
#endif
#ifndef PASS_SHADOW
#if defined(OPT_AMBIENT) && defined(TYPE_ENTITY)
vec3 calcAmbient(vec3 n) {
vec3 sqr = n * n;
@@ -55,10 +64,13 @@ uniform vec4 uBasis[32 * 2];
sqr.z * mix(uAmbient[5], uAmbient[4], pos.z);
}
#endif
#endif
#endif
#ifdef VERTEX
#if defined(PASS_COMPOSE) && defined(TYPE_ROOM)
uniform vec2 uAnimTexRanges[MAX_RANGES];
uniform vec2 uAnimTexOffsets[MAX_OFFSETS];
#endif
attribute vec4 aCoord;
attribute vec4 aTexCoord;
attribute vec4 aParam;
@@ -75,8 +87,8 @@ uniform vec4 uBasis[32 * 2];
return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + v * q.w);
}
vec3 mulBasis(vec4 rot, vec4 pos, vec3 v) {
return mulQuat(rot, v) + pos.xyz;
vec3 mulBasis(vec4 rot, vec3 pos, vec3 v) {
return mulQuat(rot, v) + pos;
}
vec4 _transform() {
@@ -92,9 +104,9 @@ uniform vec4 uBasis[32 * 2];
vec4 coord;
coord.w = rBasisPos.w; // visible flag
#ifdef TYPE_SPRITE
coord.xyz = mulBasis(rBasisRot, rBasisPos + aCoord.xyz, vec3(aTexCoord.z, -aTexCoord.w, 0.0) * 32767.0);
coord.xyz = mulBasis(rBasisRot, rBasisPos.xyz + aCoord.xyz, vec3(aTexCoord.z, -aTexCoord.w, 0.0) * 32767.0);
#else
coord.xyz = mulBasis(rBasisRot, rBasisPos, aCoord.xyz);
coord.xyz = mulBasis(rBasisRot, rBasisPos.xyz, aCoord.xyz);
#endif
#ifndef PASS_SHADOW
@@ -138,7 +150,7 @@ uniform vec4 uBasis[32 * 2];
#endif
#ifdef TYPE_MIRROR
vDiffuse.xyz *= vec3(0.3, 0.3, 2.0); // blue color dodge for crystal
vDiffuse.xyz = vec3(0.5, 0.5, 2.0); // blue color dodge for crystal
#endif
#ifdef TYPE_FLASH
@@ -217,11 +229,13 @@ uniform vec4 uBasis[32 * 2];
void _uv(vec3 coord) {
vTexCoord = aTexCoord;
#if defined(PASS_COMPOSE) && !defined(TYPE_SPRITE)
// animated texture coordinates
vec2 range = uAnimTexRanges[int(aParam.x)]; // x - start index, y - count
float frame = fract((aParam.y + uParam.x * 4.0 - range.x) / range.y) * range.y;
vec2 offset = uAnimTexOffsets[int(range.x + frame)]; // texCoord offset from first frame
vTexCoord.xy += offset;
#ifdef TYPE_ROOM
// animated texture coordinates
vec2 range = uAnimTexRanges[int(aParam.x)]; // x - start index, y - count
float frame = fract((aParam.y + uParam.x * 4.0 - range.x) / range.y) * range.y;
vec2 offset = uAnimTexOffsets[int(range.x + frame)]; // texCoord offset from first frame
vTexCoord.xy += offset;
#endif
vTexCoord.xy *= vTexCoord.zw;
#endif
@@ -464,7 +478,7 @@ uniform vec4 uBasis[32 * 2];
#endif
#ifdef TYPE_ENTITY
color.xyz += calcSpecular(n, vViewVec.xyz, vLightVec.xyz, uLightColor[0], uMaterial.z * rShadow + 0.03);
color.xyz += calcSpecular(n, vViewVec.xyz, vLightVec.xyz, uLightColor[0], (uMaterial.z + 0.03) * rShadow);
#endif
#endif

View File

@@ -8,7 +8,6 @@
#ifdef __EMSCRIPTEN__ // TODO: http streaming
#undef DECODE_MP3
#undef DECODE_OGG
#endif
#include "utils.h"
@@ -29,11 +28,15 @@ namespace Sound {
short L, R;
};
namespace Filter {
#define MAX_FDN 32
#define MAX_DELAY 2048
struct FrameHI {
int L, R;
};
static const short FDN[MAX_FDN] = {281,331,373,419,461,503,547,593,641,683,727,769,811,853,907,953,997,1039,1087,1129,1171,1213,1259,1301,1361,1409,1451,1493,1543,1597,1657,1699};
namespace Filter {
#define MAX_FDN 16
#define MAX_DELAY 1024
static const short FDN[MAX_FDN] = {281,331,373,419,461,503,547,593,641,683,727,769,811,853,907,953};
struct Delay {
int index;
@@ -83,7 +86,7 @@ namespace Sound {
void setRoomSize(const vec3 &size) {
float S = (size.x * size.z + size.x * size.y + size.z * size.y) * 2;
float V = size.x * size.y * size.z;
float f = 0.07f; // brick absorption
float f = 0.1f; // absorption factor
float rt60 = 0.161f * (V / (S * f));
float k = -10.0f / (44100.0f * rt60);
@@ -93,22 +96,23 @@ namespace Sound {
}
};
void process(Frame *frames, int count) {
void process(FrameHI *frames, int count) {
float buffer[MAX_FDN];
for (int i = 0; i < count; i++) {
Frame &frame = frames[i];
float L = float(frame.L);
float R = float(frame.R);
FrameHI &frame = frames[i];
float L = frame.L * (1.0f / 32768.0f);
float R = frame.R * (1.0f / 32768.0f);
float in = (L + R) * 0.5f;
float out = 0.0f;
// apply delay & absorption filters
for (int j = 0; j < MAX_FDN; j++) {
buffer[j] = in + output[j];
buffer[j] = df[j].process(buffer[j], FDN[j]);
buffer[j] = af[j].process(buffer[j], absCoeff[j][0], absCoeff[j][1]);
out += buffer[j] * (2.0f / MAX_FDN);
float k = in + output[j];
k = df[j].process(k, FDN[j]);
k = af[j].process(k, absCoeff[j][0], absCoeff[j][1]);
out += k * (2.0f / MAX_FDN);
buffer[j] = k;
}
// apply pan
@@ -118,8 +122,8 @@ namespace Sound {
R += buffer[j] * panCoeff[j][1];
}
frame.L = clamp(int(L), -32768, 32767);
frame.R = clamp(int(R), -32768, 32767);
frame.L = int(L * 32768.0f);
frame.R = int(R * 32768.0f);
}
}
};
@@ -259,16 +263,16 @@ namespace Sound {
int s = (s1 * inc[pred] + s2 * dec[pred]) >> 6;
s = clamp((value >> shift) + s, -32768, 32767);
s2 = s1;
s2 = s1;
s1 = s;
}
void resample(Frame *frames, short value) {
predicate(value);
frames[0].L = frames[0].R = s2 + (s1 - s2) / 4; // 0.25
frames[1].L = frames[1].R = s2 + (s1 - s2) / 2; // 0.50
frames[2].L = frames[2].R = s2 + (s1 - s2) * 3 / 4; // 0.75
frames[3].L = frames[3].R = s1; // 1.00
frames[0].L = frames[0].R = s2 + (s1 - s2) / 4; // 0.25
frames[1].L = frames[1].R = s2 + (s1 - s2) / 2; // 0.50
frames[2].L = frames[2].R = s2 + (s1 - s2) * 3 / 4; // 0.75
frames[3].L = frames[3].R = s1; // 1.00
}
int processBlock() {
@@ -401,7 +405,6 @@ namespace Sound {
struct Listener {
mat4 matrix;
// vec3 velocity;
} listener;
enum Flags {
@@ -411,19 +414,22 @@ namespace Sound {
REPLAY = 8,
SYNC = 16,
STATIC = 32,
MUSIC = 64,
};
struct Sample {
Decoder *decoder;
vec3 pos;
vec3 velocity;
float volume;
float volumeTarget;
float volumeDelta;
float pitch;
int flags;
int id;
bool isPlaying;
bool stopAfterFade;
Sample(Stream *stream, const vec3 &pos, float volume, float pitch, int flags, int id) : decoder(NULL), pos(pos), volume(volume), pitch(pitch), flags(flags), id(id) {
Sample(Stream *stream, const vec3 &pos, float volume, float pitch, int flags, int id) : decoder(NULL), pos(pos), volume(volume), volumeTarget(volume), volumeDelta(0.0f), pitch(pitch), flags(flags), id(id) {
uint32 fourcc;
stream->read(fourcc);
if (fourcc == FOURCC("RIFF")) { // wav
@@ -472,7 +478,7 @@ namespace Sound {
decoder = new VAG(stream);
}
#endif
isPlaying = decoder != NULL;
ASSERT(isPlaying);
}
@@ -481,9 +487,22 @@ namespace Sound {
delete decoder;
}
vec3 getPan() {
void setVolume(float value, float time) {
if (value < 0.0f) {
stopAfterFade = true;
value = 0.0f;
} else
stopAfterFade = false;
volumeTarget = value;
volumeDelta = volumeTarget - volume;
if (time > 0.0f)
volumeDelta /= 44100.0f * time;
}
vec2 getPan() {
if (!(flags & PAN))
return vec3(1.0f);
return vec2(1.0f);
mat4 m = Sound::listener.matrix;
vec3 v = pos - m.offset.xyz;
@@ -493,11 +512,12 @@ namespace Sound {
float l = min(1.0f, 1.0f - pan);
float r = min(1.0f, 1.0f + pan);
return vec3(l, r, 1.0f) * dist;
return vec2(l, r) * dist;
}
bool render(Frame *frames, int count) {
if (!isPlaying) return 0;
if (!isPlaying) return false;
// decode
int i = 0;
while (i < count) {
int res = decoder->decode(&frames[i], count - i);
@@ -510,23 +530,49 @@ namespace Sound {
}
i += res;
}
vec3 pan = getPan() * volume;
if (pan.x < 1.0f || pan.y < 1.0f)
for (int j = 0; j < i; j++) {
frames[j].L = int(frames[j].L * pan.x);
frames[j].R = int(frames[j].R * pan.y);
// apply volume
vec2 pan = getPan();
vec2 vol = pan * volume;
for (int j = 0; j < i; j++) {
if (volumeDelta != 0.0f) { // increase / decrease channel volume
volume += volumeDelta;
if ((volumeDelta < 0.0f && volume < volumeTarget) ||
(volumeDelta > 0.0f && volume > volumeTarget)) {
volume = volumeTarget;
volumeDelta = 0.0f;
if (stopAfterFade)
isPlaying = false;
}
vol = pan * volume;
}
frames[j].L = int(frames[j].L * vol.x);
frames[j].R = int(frames[j].R * vol.y);
}
return true;
}
void stop() {
isPlaying = false;
}
void replay() {
decoder->replay();
}
} *channels[SND_CHANNELS_MAX];
int channelsCount;
typedef void (Callback)(Sample *channel);
Callback *callback;
FrameHI *result;
Frame *buffer;
Filter::Reverberation reverb;
void init() {
channelsCount = 0;
callback = NULL;
buffer = NULL;
result = NULL;
#ifdef DECODE_MP3
mp3_decode_init();
#endif
@@ -538,33 +584,25 @@ namespace Sound {
#ifdef DECODE_MP3
mp3_decode_free();
#endif
delete[] buffer;
delete[] result;
}
void fill(Frame *frames, int count) {
if (!channelsCount) {
memset(frames, 0, sizeof(frames[0]) * count);
reverb.process(frames, count);
return;
}
struct FrameHI {
int L, R;
};
FrameHI *result = new FrameHI[count];
memset(result, 0, sizeof(FrameHI) * count);
void renderChannels(FrameHI *result, int count, bool music) {
int bufSize = count + count / 2;
Frame *buffer = new Frame[bufSize]; // + 50% for pitch
if (!buffer) buffer = new Frame[bufSize]; // + 50% for pitch
for (int i = 0; i < channelsCount; i++) {
if (music != ((channels[i]->flags & MUSIC) != 0))
continue;
if (channels[i]->flags & STATIC) {
vec3 d = channels[i]->pos - listener.matrix.getPos();
if (fabsf(d.x) > SND_FADEOFF_DIST || fabsf(d.y) > SND_FADEOFF_DIST || fabsf(d.z) > SND_FADEOFF_DIST)
continue;
}
if ((channels[i]->flags & LOOP) && channels[i]->volume < EPS)
if ((channels[i]->flags & LOOP) && channels[i]->volume < EPS && channels[i]->volumeTarget < EPS)
continue;
memset(buffer, 0, sizeof(Frame) * bufSize);
@@ -586,19 +624,34 @@ namespace Sound {
}
}
}
}
void fill(Frame *frames, int count) {
if (!channelsCount) {
memset(frames, 0, sizeof(frames[0]) * count);
//if (Core::settings.audio.reverb)
// reverb.process(frames, count);
return;
}
if (!result) result = new FrameHI[count];
memset(result, 0, sizeof(FrameHI) * count);
renderChannels(result, count, false);
if (Core::settings.audio.reverb)
reverb.process(result, count);
renderChannels(result, count, true);
for (int i = 0; i < count; i++) {
frames[i].L = clamp(result[i].L, -32768, 32767);
frames[i].R = clamp(result[i].R, -32768, 32767);
}
reverb.process(frames, count);
delete[] buffer;
delete[] result;
for (int i = 0; i < channelsCount; i++)
if (!channels[i]->isPlaying) {
if (callback) callback(channels[i]);
delete channels[i];
channels[i] = channels[--channelsCount];
i--;
@@ -635,7 +688,7 @@ namespace Sound {
channels[i]->pos = pos;
channels[i]->pitch = pitch;
if (flags & (REPLAY | UNIQUE))
channels[i]->decoder->replay();
channels[i]->replay();
delete stream;
return channels[i];
}

View File

@@ -13,8 +13,9 @@ struct Sprite : Controller {
bool instant;
int frame, flag;
float time;
vec3 velocity;
Sprite(IGame *game, int entity, bool instant = true, int frame = FRAME_ANIMATED) : Controller(game, entity), instant(instant), flag(frame), time(0.0f) {
Sprite(IGame *game, int entity, bool instant = true, int frame = FRAME_ANIMATED) : Controller(game, entity), instant(instant), flag(frame), time(0), velocity(0) {
if (frame >= 0) { // specific frame
this->frame = frame;
} else if (frame == FRAME_RANDOM) { // random frame
@@ -30,6 +31,8 @@ struct Sprite : Controller {
if (index > -1) {
level->entities[index].intensity = 0x1FFF - level->rooms[room].ambient;
level->entities[index].controller = empty ? NULL : new Sprite(game, index, true, frame);
if (level->entities[index].controller)
((Controller*)level->entities[index].controller)->activate();
}
return index;
}
@@ -55,6 +58,8 @@ struct Sprite : Controller {
if (instant && time >= (1.0f / SPRITE_FPS))
remove = true;
pos += velocity * (30.0f * Core::deltaTime);
if (remove) {
level->entityRemove(entity);
delete this;
@@ -62,8 +67,10 @@ struct Sprite : Controller {
}
virtual void render(Frustum *frustum, MeshBuilder *mesh, Shader::Type type, bool caustics) {
Core::setBlending(bmAlpha);
Core::active.shader->setParam(uBasis, Basis(Core::mViewInv.getRot(), pos));
mesh->renderSprite(-(getEntity().modelIndex + 1), frame);
Core::setBlending(bmNone);
}
};

View File

@@ -11,7 +11,7 @@ struct Texture {
Format format;
bool cube;
Texture(int width, int height, Format format, bool cube, void *data = NULL, bool filter = true, bool mips = false) : cube(cube) {
Texture(int width, int height, Format format, bool cube = false, void *data = NULL, bool filter = true, bool mips = false) : cube(cube) {
if (!Core::support.texNPOT) {
width = nextPow2(width);
height = nextPow2(height);
@@ -84,12 +84,45 @@ struct Texture {
{ GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT }, // SHADOW
};
FormatDesc &desc = formats[format];
FormatDesc desc = formats[format];
#ifdef __EMSCRIPTEN__ // fucking firefox!
if (format == RGBA_FLOAT) {
if (Core::support.texFloat) {
desc.ifmt = GL_RGBA;
desc.type = GL_FLOAT;
}
}
if (format == RGBA_HALF) {
if (Core::support.texHalf) {
desc.ifmt = GL_RGBA;
#ifdef MOBILE
desc.type = GL_HALF_FLOAT_OES;
#endif
}
}
#else
if ((format == RGBA_FLOAT && !Core::support.colorFloat) || (format == RGBA_HALF && !Core::support.colorHalf)) {
desc.ifmt = GL_RGBA;
#ifdef MOBILE
if (format == RGBA_HALF)
desc.type = GL_HALF_FLOAT_OES;
#endif
}
#endif
for (int i = 0; i < 6; i++) {
glTexImage2D(cube ? (GL_TEXTURE_CUBE_MAP_POSITIVE_X + i) : GL_TEXTURE_2D, 0, desc.ifmt, width, height, 0, desc.fmt, desc.type, data);
if (!cube) break;
}
if (mips) {
glGenerateMipmap(target);
if (!cube && filter && Core::support.texAniso)
glTexParameteri(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, min(int(Core::support.texAniso), 8));
//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 4);
}
}
virtual ~Texture() {
@@ -111,6 +144,275 @@ struct Texture {
glBindTexture(cube ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D, 0);
}
}
static Texture* LoadPCX(Stream &stream) {
struct Color24 {
uint8 r, g, b;
};
struct Color32 {
uint8 r, g, b, a;
};
struct PCX {
uint8 magic;
uint8 version;
uint8 compression;
uint8 bpp;
uint16 rect[4];
uint16 width;
uint16 height;
uint8 other[48 + 64];
} pcx;
stream.raw(&pcx, sizeof(PCX));
ASSERT(pcx.bpp == 8);
ASSERT(pcx.compression == 1);
int i = 0;
int size = pcx.width * pcx.height;
int dw = Core::support.texNPOT ? pcx.width : nextPow2(pcx.width);
int dh = Core::support.texNPOT ? pcx.height : nextPow2(pcx.height);
uint8 *buffer = new uint8[size + 256 * 3 + dw * dh * 4];
while (i < size) {
uint8 n;
stream.read(n);
if ((n & 0xC0) == 0xC0) {
uint8 count = n & 0x3F;
stream.read(n);
memset(&buffer[i], n, count);
i += count;
} else
buffer[i++] = n;
}
uint8 flag;
stream.read(flag);
ASSERT(flag == 0x0C);
Color24 *palette = (Color24*)&buffer[size];
stream.raw(palette, 256 * 3);
Color32 *data = (Color32*)&palette[256];
memset(data, 0, dw * dh * 4);
// TODO: color bleeding
Color32 *ptr = data;
i = 0;
for (int y = 0; y < pcx.height; y++) {
for (int x = 0; x < pcx.width; x++) {
Color24 &c = palette[buffer[i++]];
ptr[x].r = c.r;
ptr[x].g = c.g;
ptr[x].b = c.b;
ptr[x].a = 255;
}
ptr += dw;
}
Texture *tex = new Texture(dw, dh, Texture::RGBA, false, data);
delete[] buffer;
return tex;
}
};
#define ATLAS_BORDER 8
struct Atlas {
typedef void (Callback)(int id, int width, int height, int x, int y, void *userData, void *data);
struct Node {
Node *child[2];
short4 rect;
int id;
Node(short l, short t, short r, short b) : rect(l, t, r, b), id(-1) {
child[0] = child[1] = NULL;
}
~Node() {
delete child[0];
delete child[1];
}
Node* insert(const short4 &tile, int id) {
ASSERT(tile.x != 0x7FFF);
if (child[0] != NULL && child[1] != NULL) {
Node *node = child[0]->insert(tile, id);
if (node != NULL) return node;
return child[1]->insert(tile, id);
} else {
if (this->id != -1)
return NULL;
int16 w = rect.z - rect.x;
int16 h = rect.w - rect.y;
int16 tx = (tile.z - tile.x) + ATLAS_BORDER * 2;
int16 ty = (tile.w - tile.y) + ATLAS_BORDER * 2;
if (w < tx || h < ty)
return NULL;
if (w == tx && h == ty) {
this->id = id;
return this;
}
int16 dx = w - tx;
int16 dy = h - ty;
if (dx > dy) {
child[0] = new Node(rect.x, rect.y, rect.x + tx, rect.w);
child[1] = new Node(rect.x + tx, rect.y, rect.z, rect.w);
} else {
child[0] = new Node(rect.x, rect.y, rect.z, rect.y + ty);
child[1] = new Node(rect.x, rect.y + ty, rect.z, rect.w);
}
return child[0]->insert(tile, id);
}
}
} *root;
struct Tile {
int id;
short4 uv;
} *tiles;
int tilesCount;
int size;
int width, height;
void *userData;
Callback *callback;
Atlas(int maxTiles, void *userData, Callback *callback) : root(NULL), tilesCount(0), size(0), userData(userData), callback(callback) {
tiles = new Tile[maxTiles];
}
~Atlas() {
delete root;
delete[] tiles;
}
void add(short4 uv, int id) {
for (int i = 0; i < tilesCount; i++)
if (tiles[i].uv == uv) {
uv.x = 0x7FFF;
uv.y = tiles[i].id;
uv.z = uv.w = 0;
break;
}
tiles[tilesCount].id = id;
tiles[tilesCount].uv = uv;
tilesCount++;
if (uv.x != 0x7FFF)
size += (uv.z - uv.x + ATLAS_BORDER * 2) * (uv.w - uv.y + ATLAS_BORDER * 2);
}
bool insertAll(int *indices) {
for (int i = 0; i < tilesCount; i++) {
int idx = indices[i];
if (tiles[idx].uv.x != 0x7FFF && !root->insert(tiles[idx].uv, tiles[idx].id))
return false;
}
return true;
}
Texture* pack() {
width = nextPow2(int(sqrtf(float(size))));
height = (width * width / 2 > size) ? (width / 2) : width;
// sort
int *indices = new int[tilesCount];
for (int i = 0; i < tilesCount; i++)
indices[i] = i;
int n = tilesCount;
bool swapped;
do {
swapped = false;
for (int i = 1; i < n; i++) {
short4 &a = tiles[indices[i - 1]].uv;
short4 &b = tiles[indices[i]].uv;
//if ((a.z - a.x + ATLAS_BORDER * 2) * (a.w - a.y + ATLAS_BORDER * 2) < (b.z - b.x + ATLAS_BORDER * 2) * (b.w - b.y + ATLAS_BORDER * 2)) {
if ((a.z - a.x + ATLAS_BORDER * 2) < (b.z - b.x + ATLAS_BORDER * 2)) {
swap(indices[i - 1], indices[i]);
swapped = true;
}
}
n--;
} while (swapped);
// pack
while (1) {
delete root;
root = new Node(0, 0, width, height);
if (insertAll(indices))
break;
if (width < height)
width *= 2;
else
height *= 2;
}
delete[] indices;
uint32 *data = new uint32[width * height];
memset(data, 0, width * height * sizeof(data[0]));
fill(root, data);
fillInstances();
Texture *atlas = new Texture(width, height, Texture::RGBA, false, data, true, true);
delete[] data;
return atlas;
};
void fill(Node *node, void *data) {
if (!node) return;
if (node->id == -1) {
fill(node->child[0], data);
fill(node->child[1], data);
} else
callback(node->id, width, height, node->rect.x, node->rect.y, userData, data);
}
void fillInstances() {
for (int i = 0; i < tilesCount; i++)
if (tiles[i].uv.x == 0x7FFF) {
callback(tiles[i].id, width, height, tiles[i].uv.y, 0, userData, NULL);
/*
TR::ObjectTexture &r = level.objectTextures[ref];
int minXr = min(min(r.texCoord[0].x, r.texCoord[1].x), r.texCoord[2].x);
int minYr = min(min(r.texCoord[0].y, r.texCoord[1].y), r.texCoord[2].y);
TR::ObjectTexture &t = level.objectTextures[tiles[i].id];
int minX = min(min(t.texCoord[0].x, t.texCoord[1].x), t.texCoord[2].x);
int maxX = max(max(t.texCoord[0].x, t.texCoord[1].x), t.texCoord[2].x);
int minY = min(min(t.texCoord[0].y, t.texCoord[1].y), t.texCoord[2].y);
int maxY = max(max(t.texCoord[0].y, t.texCoord[1].y), t.texCoord[2].y);
int cx = minXr - minX;
int cy = minYr - minY;
for (int i = 0; i < 4; i++) {
if (t.texCoord[i].x == maxX) t.texCoord[i].x++;
if (t.texCoord[i].y == maxY) t.texCoord[i].y++;
t.texCoord[i].x += cx;
t.texCoord[i].y += cy;
}
*/
}
}
};
#endif

View File

@@ -3,60 +3,56 @@
#include "core.h"
#include "controller.h"
#include "character.h"
#include "sprite.h"
struct Trigger : Controller {
struct Switch : Controller {
enum {
STATE_DOWN,
STATE_UP,
};
bool immediate;
float timer;
int baseState;
Trigger(IGame *game, int entity, bool immediate) : Controller(game, entity), immediate(immediate), timer(0.0f) {
baseState = state;
getEntity().flags.collision = false;
Switch(IGame *game, int entity) : Controller(game, entity) {}
bool setTimer(float t) {
if (activeState == asInactive) {
if (state == STATE_DOWN && t > 0.0f) {
timer = t;
activeState = asActive;
} else
deactivate(true);
return true;
}
return false;
}
bool inState() {
return (state != baseState) == (getEntity().flags.active != 0);
}
virtual bool activate(ActionCommand *cmd) {
if (this->timer != 0.0f || !inState() || actionCommand) return false;
Controller::activate(cmd);
this->timer = cmd->timer;
getEntity().flags.active ^= 0x1F;
if (immediate)
activateNext();
return true;
virtual bool activate() {
if (Controller::activate()) {
animation.setState(state == STATE_UP ? STATE_DOWN : STATE_UP);
return true;
}
return false;
}
virtual void update() {
TR::Entity &entity = getEntity();
if (timer > 0.0f) {
timer -= Core::deltaTime;
if (timer <= 0.0f) {
timer = 0.0f;
entity.flags.active ^= 0x1F;
}
}
if (timer < 0.0f) {
timer += Core::deltaTime;
if (timer >= 0.0f) {
timer = 0.0f;
entity.flags.active ^= 0x1F;
}
}
if (!inState() && entity.type != TR::Entity::KEY_HOLE_1 && entity.type != TR::Entity::PUZZLE_HOLE_1)
animation.setState(state != baseState ? baseState : (entity.type == TR::Entity::TRAP_BLADE ? 2 : (baseState ^ 1)));
updateAnimation(true);
updateEntity();
getEntity().flags.active = TR::ACTIVE;
if (!isActive())
animation.setState(STATE_UP);
}
};
struct Gear : Controller {
enum {
STATE_STATIC,
STATE_ROTATE,
};
Gear(IGame *game, int entity) : Controller(game, entity) {}
virtual void update() {
updateAnimation(true);
animation.setState(isActive() ? STATE_ROTATE : STATE_STATIC);
}
};
@@ -64,8 +60,9 @@ struct Dart : Controller {
vec3 velocity;
vec3 dir;
bool inWall; // dart starts from wall
bool armed;
Dart(IGame *game, int entity) : Controller(game, entity), inWall(true) {
Dart(IGame *game, int entity) : Controller(game, entity), inWall(true), armed(true) {
dir = vec3(sinf(angle.y), 0, cosf(angle.y));
}
@@ -73,6 +70,14 @@ struct Dart : Controller {
velocity = dir * animation.getSpeed();
pos = pos + velocity * (Core::deltaTime * 30.0f);
updateEntity();
Controller *lara = (Controller*)level->laraController;
if (armed && collide(lara)) {
Sprite::add(game, TR::Entity::BLOOD, getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z, Sprite::FRAME_ANIMATED);
lara->hit(50.0f, this);
armed = false;
}
TR::Level::FloorInfo info;
level->getFloorInfo(getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z, info);
if (pos.y > info.floor || pos.y < info.ceiling || !insideRoom(pos, getRoomIndex())) {
@@ -90,23 +95,31 @@ struct Dart : Controller {
}
};
struct Dartgun : Trigger {
vec3 origin;
struct TrapDartgun : Controller {
enum {
STATE_IDLE,
STATE_FIRE
};
Dartgun(IGame *game, int entity) : Trigger(game, entity, true), origin(pos) {}
virtual bool activate(ActionCommand *cmd) {
if (!Trigger::activate(cmd))
TrapDartgun(IGame *game, int entity) : Controller(game, entity) {}
virtual bool activate() {
if (!Controller::activate())
return false;
animation.setState(STATE_FIRE);
// add dart (bullet)
TR::Entity &entity = getEntity();
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)p.x, (int)p.y, (int)p.z, entity.rotation, entity.intensity);
if (dartIndex > -1)
level->entities[dartIndex].controller = new Dart(game, dartIndex);
if (dartIndex > -1) {
Dart *dart = new Dart(game, dartIndex);
dart->activate();
level->entities[dartIndex].controller = dart;
}
Sprite::add(game, TR::Entity::SMOKE, entity.room, (int)p.x, (int)p.y, (int)p.z);
@@ -115,23 +128,77 @@ struct Dartgun : Trigger {
return true;
}
void virtual update() {
updateAnimation(true);
if (animation.canSetState(STATE_IDLE)) {
animation.setState(STATE_IDLE);
deactivate();
}
}
};
struct Boulder : Trigger {
#define BOULDER_DAMAGE_GROUND 1000
#define BOULDER_DAMAGE_AIR 100
Boulder(IGame *game, int entity) : Trigger(game, entity, true) {}
struct TrapBoulder : Controller {
enum {
STATE_FALL,
STATE_ROLL,
};
vec3 velocity;
TrapBoulder(IGame *game, int entity) : Controller(game, entity), velocity(0) {}
virtual void update() {
if (getEntity().flags.active) {
updateAnimation(true);
updateEntity();
if (activeState != asActive) return;
TR::Level::FloorInfo info;
level->getFloorInfo(getRoomIndex(), int(pos.x), int(pos.y), int(pos.z), info);
vec3 dir = getDir();
if (pos.y >= info.floor - 256) {
pos.y = float(info.floor);
velocity = dir * animation.getSpeed();
if (state != STATE_ROLL)
animation.setState(STATE_ROLL);
} else {
if (velocity.y == 0.0f)
velocity.y = 10.0f;
velocity.y += GRAVITY * Core::deltaTime;
animation.setState(STATE_FALL);
}
vec3 p = pos;
pos += velocity * (30.0f * Core::deltaTime);
if (info.roomNext != TR::NO_ROOM)
getEntity().room = info.roomNext;
vec3 v = pos + getDir() * 512.0f;
level->getFloorInfo(getRoomIndex(), int(v.x), int(v.y), int(v.z), info);
if (pos.y > info.floor) {
pos = p;
deactivate();
return;
}
Character *lara = (Character*)level->laraController;
if (lara->health > 0.0f && collide(lara)) {
if (lara->stand == Character::STAND_GROUND)
lara->hit(BOULDER_DAMAGE_GROUND, this, TR::HIT_BOULDER);
if (lara->stand == Character::STAND_AIR)
lara->hit(BOULDER_DAMAGE_AIR * 30.0f * Core::deltaTime, this);
}
updateAnimation(true);
updateEntity();
}
};
// not a trigger
struct Block : Controller {
enum {
STATE_STAND = 1,
STATE_PUSH,
@@ -189,6 +256,7 @@ struct Block : Controller {
if (!animation.setState(push ? STATE_PUSH : STATE_PULL))
return false;
updateFloor(false);
activate();
return true;
}
@@ -198,21 +266,27 @@ struct Block : Controller {
if (state == STATE_STAND) {
updateEntity();
updateFloor(true);
deactivate();
}
updateLights();
}
};
struct MovingBlock : Trigger {
int lastState;
struct MovingBlock : Controller {
enum {
STATE_BEGIN,
STATE_END,
STATE_MOVE,
};
MovingBlock(IGame *game, int entity) : Trigger(game, entity, true) {
lastState = state;
updateFloor(true);
MovingBlock(IGame *game, int entity) : Controller(game, entity) {
if (!getEntity().flags.invisible)
updateFloor(true);
}
void updateFloor(bool rise) {
updateEntity();
TR::Entity &e = getEntity();
TR::Level::FloorInfo info;
level->getFloorInfo(e.room, e.x, e.y, e.z, info);
@@ -222,123 +296,191 @@ struct MovingBlock : Trigger {
TR::Room::Sector &s = level->getSector(e.room, e.x, e.z, dx, dz);
s.floor += rise ? -8 : 8;
}
virtual void updateAnimation(bool commands) {
Trigger::updateAnimation(commands);
if (state != lastState) {
switch (lastState = state) {
case 0 :
case 1 : updateFloor(true); break;
case 2 : updateFloor(false); break;
virtual void update() {
updateAnimation(true);
if (isActive()) {
if (state == STATE_BEGIN) {
updateFloor(false);
animation.setState(STATE_END);
}
} else {
if (state == STATE_END) {
updateFloor(false);
animation.setState(STATE_BEGIN);
}
}
if (activeState == asInactive) {
if (getEntity().flags.active == TR::ACTIVE)
activeState = asActive; // stay in active items list
pos.x = int(pos.x / 1024.0f) * 1024.0f + 512.0f;
pos.z = int(pos.z / 1024.0f) * 1024.0f + 512.0f;
updateFloor(true);
return;
}
pos += getDir() * (animation.getSpeed() * Core::deltaTime * 30.0f);
}
};
struct Door : Trigger {
int8 *floor[2], orig[2];
uint16 box;
Door(IGame *game, int entity) : Trigger(game, entity, true) {
TR::Entity &e = getEntity();
TR::Level::FloorInfo info;
vec3 p = pos - getDir() * 1024.0f;
struct Door : Controller {
enum {
STATE_CLOSE,
STATE_OPEN,
};
level->getFloorInfo(e.room, (int)p.x, (int)p.y, (int)p.z, info);
box = info.boxIndex;
struct BlockInfo {
int roomIndex[2];
int sectorIndex[2];
TR::Room::Sector sectors[2];
int dx, dz;
TR::Room::Sector *s = &level->getSector(e.room, (int)p.x, (int)p.z, dx, dz);
BlockInfo() {}
BlockInfo(TR::Level *level, int room, int nx, int nz, int x, int z, bool flip) {
// front
roomIndex[0] = room;
roomIndex[1] = TR::NO_ROOM;
orig[0] = *(floor[0] = &s->floor);
if (roomIndex[0] == TR::NO_ROOM)
return;
if (flip && level->rooms[roomIndex[0]].alternateRoom != -1)
roomIndex[0] = level->rooms[roomIndex[0]].alternateRoom;
if (info.roomNext != 0xFF) {
s = &level->getSector(info.roomNext, e.x, e.z, dx, dz);
orig[1] = *(floor[1] = &s->floor);
} else
floor[1] = NULL;
sectors[0] = level->getSector(roomIndex[0], x, z, sectorIndex[0]);
updateBlock();
}
// behind
roomIndex[1] = level->getNextRoom(sectors[0].floorIndex);
void updateBlock() {
int8 v[2];
if (getEntity().flags.active) {
v[0] = orig[0];
v[1] = orig[1];
} else
v[0] = v[1] = TR::FLOOR_BLOCK;
if (roomIndex[1] == TR::NO_ROOM)
return;
if (flip && level->rooms[roomIndex[1]].alternateRoom != -1)
roomIndex[1] = level->rooms[roomIndex[1]].alternateRoom;
if (box != 0xFFFF) {
TR::Box &b = level->boxes[box];
if (b.overlap.blockable)
b.overlap.block = !getEntity().flags.active;
sectors[1] = level->getSector(roomIndex[1], nx, nz, sectorIndex[1]);
}
if (floor[0]) *floor[0] = v[0];
if (floor[1]) *floor[1] = v[1];
void set(TR::Level *level) {
for (int i = 0; i < 2; i++)
if (roomIndex[i] != TR::NO_ROOM) {
TR::Room::Sector &s = level->rooms[roomIndex[i]].sectors[sectorIndex[i]];
s.floorIndex = 0;
s.boxIndex = TR::NO_BOX;
s.roomBelow = TR::NO_ROOM;
s.floor = TR::NO_FLOOR;
s.roomAbove = TR::NO_ROOM;
s.ceiling = TR::NO_FLOOR;
if (sectors[i].boxIndex != TR::NO_BOX) {
TR::Box &box = level->boxes[sectors[i].boxIndex];
if (box.overlap.blockable)
box.overlap.block = true;
}
}
}
void reset(TR::Level *level) {
for (int i = 0; i < 2; i++)
if (roomIndex[i] != TR::NO_ROOM) {
level->rooms[roomIndex[i]].sectors[sectorIndex[i]] = sectors[i];
if (sectors[i].boxIndex != TR::NO_BOX) {
TR::Box &box = level->boxes[sectors[i].boxIndex];
if (box.overlap.blockable)
box.overlap.block = false;
}
}
}
} block[2];
Door(IGame *game, int entity) : Controller(game, entity) {
TR::Entity &e = getEntity();
vec3 p = pos - getDir() * 1024.0f;
block[0] = BlockInfo(level, e.room, e.x, e.z, int(p.x), int(p.z), false);
block[1] = BlockInfo(level, e.room, e.x, e.z, int(p.x), int(p.z), true);
updateBlock(false);
}
virtual bool activate(ActionCommand *cmd) {
bool res = Trigger::activate(cmd);
updateBlock();
return res;
void updateBlock(bool open) {
if (open) {
block[0].reset(level);
block[1].reset(level);
} else {
block[0].set(level);
block[1].set(level);
}
}
virtual void update() {
updateAnimation(true);
int targetState = isActive() ? STATE_OPEN : STATE_CLOSE;
if (state == targetState)
updateBlock(targetState == STATE_OPEN);
else
animation.setState(targetState);
}
};
struct TrapDoor : Trigger {
struct TrapDoor : Controller {
enum {
STATE_CLOSE,
STATE_OPEN,
};
TrapDoor(IGame *game, int entity) : Trigger(game, entity, true) {
TrapDoor(IGame *game, int entity) : Controller(game, entity) {
getEntity().flags.collision = true;
}
virtual void update() {
updateAnimation(true);
int targetState = isActive() ? STATE_OPEN : STATE_CLOSE;
virtual bool activate(ActionCommand *cmd) {
bool res = Trigger::activate(cmd);
getEntity().flags.collision = !getEntity().flags.active;
return res;
if (state == targetState)
getEntity().flags.collision = targetState == STATE_CLOSE;
else
animation.setState(targetState);
}
};
struct TrapFloor : Trigger {
struct TrapFloor : Controller {
enum {
STATE_STATIC,
STATE_SHAKE,
STATE_FALL,
STATE_DOWN,
};
float velocity;
float speed;
TrapFloor(IGame *game, int entity) : Trigger(game, entity, true), velocity(0) {
TR::Entity &e = getEntity();
e.flags.collision = true;
TrapFloor(IGame *game, int entity) : Controller(game, entity), speed(0) {
getEntity().flags.collision = true;
}
virtual bool activate(ActionCommand *cmd) {
TR::Entity &e = level->entities[cmd->emitter];
if (e.type != TR::Entity::LARA) return true;
int ey = (int)pos.y - 512; // real floor object position
return (abs(e.y - ey) <= 8) ? Trigger::activate(cmd) : true;
virtual bool activate() {
if (state != STATE_STATIC) return false;
TR::Entity &e = ((Controller*)level->laraController)->getEntity();
int ey = getEntity().y - 512; // real floor object position
if (abs(e.y - ey) <= 8 && Controller::activate()) {
animation.setState(STATE_SHAKE);
return true;
}
return false;
}
virtual void update() {
Trigger::update();
updateAnimation(true);
if (state == STATE_FALL) {
TR::Entity &e = getEntity();
e.flags.collision = false;
velocity += GRAVITY * 30 * Core::deltaTime;
pos.y += velocity * Core::deltaTime;
getEntity().flags.collision = false;
speed += GRAVITY * 30 * Core::deltaTime;
pos.y += speed * Core::deltaTime;
TR::Level::FloorInfo info;
level->getFloorInfo(e.room, e.x, (int)pos.y, e.z, info);
level->getFloorInfo(getRoomIndex(), int(pos.x), int(pos.y), int(pos.z), info);
if (pos.y > info.roomFloor && info.roomBelow != 0xFF)
e.room = info.roomBelow;
getEntity().room = info.roomBelow;
if (pos.y > info.floor) {
pos.y = (float)info.floor;
@@ -349,10 +491,8 @@ struct TrapFloor : Trigger {
}
};
struct Bridge : Trigger {
Bridge(IGame *game, int entity) : Trigger(game, entity, true) {
struct Bridge : Controller {
Bridge(IGame *game, int entity) : Controller(game, entity) {
getEntity().flags.collision = true;
}
};
@@ -362,12 +502,17 @@ struct Crystal : Controller {
Crystal(IGame *game, int entity) : Controller(game, entity) {
environment = new Texture(64, 64, Texture::RGBA, true);
activate();
}
virtual ~Crystal() {
delete environment;
}
virtual void update() {
updateAnimation(false);
}
virtual void render(Frustum *frustum, MeshBuilder *mesh, Shader::Type type, bool caustics) {
Shader *sh = Core::active.shader;
sh->setParam(uMaterial, vec4(1.0f));
@@ -376,23 +521,145 @@ struct Crystal : Controller {
}
};
struct Waterfall : Trigger {
#define BLADE_DAMAGE 100
#define BLADE_RANGE 1024
struct TrapBlade : Controller {
enum {
STATE_STATIC = 0,
STATE_SWING = 2,
};
TrapBlade(IGame *game, int entity) : Controller(game, entity) {}
virtual void update() {
updateAnimation(true);
if (isActive()) {
if (state == STATE_STATIC)
animation.setState(STATE_SWING);
} else {
if (state == STATE_SWING)
animation.setState(STATE_STATIC);
}
if (state != STATE_SWING)
return;
int f = animation.frameIndex;
if ((f <= 8 || f >= 20) && (f <= 42 || f >= 57))
return;
Character* lara = (Character*)level->laraController;
if (!checkRange(lara, BLADE_RANGE) || !collide(lara))
return;
lara->hit(BLADE_DAMAGE * 30.0f * Core::deltaTime, this, TR::HIT_BLADE);
}
};
#define SPIKES_DAMAGE_FALL 1000
#define SPIKES_DAMAGE_RUN 15
#define SPIKES_RANGE 1024
struct TrapSpikes : Controller {
TrapSpikes(IGame *game, int entity) : Controller(game, entity) {
activate();
}
virtual void update() {
Character *lara = (Character*)level->laraController;
if (lara->health <= 0.0f) return;
if (!checkRange(lara, SPIKES_RANGE) || !collide(lara))
return;
if (lara->stand != Character::STAND_AIR || lara->velocity.y <= 0.0f || (pos.y - lara->pos.y) > 256.0f) {
if (lara->speed < 30.0f)
return;
lara->hit(SPIKES_DAMAGE_RUN * 30.0f * Core::deltaTime, this, TR::HIT_SPIKES);
} else
lara->hit(SPIKES_DAMAGE_FALL, this, TR::HIT_SPIKES);
}
};
struct TrapCeiling : Controller {
enum {
STATE_STATIC,
STATE_FALL,
STATE_DOWN,
};
float speed;
TrapCeiling(IGame *game, int entity) : Controller(game, entity), speed(0) {}
virtual void update() {
updateAnimation(true);
if (state == STATE_STATIC)
animation.setState(STATE_FALL);
if (state == STATE_FALL) {
speed += GRAVITY * 30 * Core::deltaTime;
pos.y += speed * Core::deltaTime;
TR::Level::FloorInfo info;
level->getFloorInfo(getRoomIndex(), int(pos.x), int(pos.y), int(pos.z), info);
if (pos.y > info.roomFloor && info.roomBelow != 0xFF)
getEntity().room = info.roomBelow;
if (pos.y > info.floor) {
pos.y = (float)info.floor;
animation.setState(STATE_DOWN);
}
updateEntity();
Controller *lara = (Controller*)level->laraController;
if (collide(lara))
lara->hit(1000);
}
}
};
struct TrapSword : Controller {
TrapSword(IGame *game, int entity) : Controller(game, entity) {}
virtual void update() {
updateAnimation(true);
}
};
struct KeyHole : Controller {
KeyHole(IGame *game, int entity) : Controller(game, entity) {}
virtual bool activate() {
if (!Controller::activate()) return false;
getEntity().flags.active = TR::ACTIVE;
if (getEntity().isPuzzleHole()) {
int doneIdx = TR::Entity::convToInv(TR::Entity::getItemForHole(getEntity().type)) - TR::Entity::INV_PUZZLE_1;
meshSwap(0, level->extra.puzzleDone[doneIdx]);
}
deactivate();
return true;
}
virtual void update() {}
};
struct Waterfall : Controller {
#define SPLASH_TIMESTEP (1.0f / 30.0f)
float timer;
bool drop;
float dropRadius;
float dropStrength;
vec3 dropPos;
Waterfall(IGame *game, int entity) : Trigger(game, entity, true), timer(0.0f) {}
Waterfall(IGame *game, int entity) : Controller(game, entity), timer(0.0f) {}
virtual void update() {
drop = false;
Trigger::update();
if (!getEntity().flags.active) return;
updateAnimation(true);
vec3 delta = (((Controller*)level->cameraController)->pos - pos) * (1.0f / 1024.0f);
vec3 delta = (((ICamera*)level->cameraController)->pos - pos) * (1.0f / 1024.0f);
if (delta.length2() > 100.0f)
return;
@@ -400,12 +667,12 @@ struct Waterfall : Trigger {
if (timer > 0.0f) return;
timer += SPLASH_TIMESTEP * (1.0f + randf() * 0.25f);
drop = true;
dropRadius = randf() * 128.0f + 128.0f;
dropStrength = randf() * 0.1f + 0.05f;
float dropRadius = randf() * 128.0f + 128.0f;
float dropStrength = randf() * 0.1f + 0.05f;
vec2 p = (vec2(randf(), randf()) * 2.0f - 1.0f) * (512.0f - dropRadius);
dropPos = pos + vec3(p.x, 0.0f, p.y);
vec3 dropPos = pos + vec3(p.x, 0.0f, p.y);
game->waterDrop(dropPos, dropRadius, dropStrength);
Sprite::add(game, TR::Entity::WATER_SPLASH, getRoomIndex(), (int)dropPos.x, (int)dropPos.y, (int)dropPos.z);
}
@@ -429,6 +696,7 @@ struct Bubble : Sprite {
room = s.roomAbove;
}
time -= (e.y - h) / speed - (1.0f / SPRITE_FPS);
activate();
}
virtual ~Bubble() {
@@ -439,8 +707,8 @@ struct Bubble : Sprite {
pos.y -= speed * Core::deltaTime;
angle.x += 30.0f * 13.0f * DEG2RAD * Core::deltaTime;
angle.y += 30.0f * 9.0f * DEG2RAD * Core::deltaTime;
pos.x += sinf(angle.y) * 11.0f * 30.0f * Core::deltaTime;
pos.z += cosf(angle.x) * 8.0f * 30.0f * Core::deltaTime;
pos.x += sinf(angle.y) * (11.0f * 30.0f * Core::deltaTime);
pos.z += cosf(angle.x) * (8.0f * 30.0f * Core::deltaTime);
updateEntity();
Sprite::update();
}

194
src/ui.h
View File

@@ -4,6 +4,125 @@
#include "core.h"
#include "controller.h"
enum StringID {
STR_NOT_IMPLEMENTED
// help
, STR_LOADING
, STR_HELP_PRESS
, STR_HELP_TEXT
// inventory pages
, STR_OPTION
, STR_INVENTORY
, STR_ITEMS
// inventory option
, STR_GAME
, STR_MAP
, STR_COMPASS
, STR_HOME
, STR_DETAIL
, STR_SOUND
, STR_CONTROLS
, STR_GAMMA
// passport menu
, STR_AUTOSAVE
, STR_LOAD_GAME
, STR_START_GAME
, STR_RESTART_LEVEL
, STR_EXIT_TO_TITLE
, STR_EXIT_GAME
, STR_SELECT_LEVEL
// inventory items
, STR_UNKNOWN
, STR_PISTOLS
, STR_SHOTGUN
, STR_MAGNUMS
, STR_UZIS
, STR_AMMO_PISTOLS
, STR_AMMO_SHOTGUN
, STR_AMMO_MAGNUMS
, STR_AMMO_UZIS
, STR_MEDI_SMALL
, STR_MEDI_BIG
, STR_PUZZLE
, STR_KEY
, STR_LEAD_BAR
, STR_SCION
, STR_MAX
};
const char *helpText =
"Controls gamepad, touch and keyboard:@"
" H - Show or hide this help@"
" TAB - Inventory@"
" LEFT - Left@"
" RIGHT - Right@"
" UP - Run@"
" DOWN - Back@"
" SHIFT - Walk@"
" SPACE - Draw Weapon@"
" CTRL - Action@"
" D - Jump@"
" Z - Step Left@"
" X - Step Right@"
" A - Roll@"
" C - Look # not implemented #@"
" V - First Person View@"
" R - slow motion@"
" T - fast motion@"
" ALT + ENTER - Fullscreen@@"
"Actions:@"
" Out of water - Run + Action@"
" Handstand - Run + Walk@"
" Swan dive - Run + Walk + jump@"
" DOZY on - Look + Step Right + Action + Jump@"
" DOZY off - Walk@";
const char *STR[STR_MAX] = {
"Not implemented yet!"
// help
, "Loading..."
, "Press H for help"
, helpText
// inventory pages
, "OPTION"
, "INVENTORY"
, "ITEMS"
// inventory option
, "Game"
, "Map"
, "Compass"
, "Lara's Home"
, "Detail Levels"
, "Sound"
, "Controls"
, "Gamma"
// passport options
, "Autosave"
, "Load Game"
, "Start Game"
, "Restart Level"
, "Exit to Title"
, "Exit Game"
, "Select Level"
// inventory items
, "Unknown"
, "Pistols"
, "Shotgun"
, "Magnums"
, "Uzis"
, "Pistol Clips"
, "Shotgun Shells"
, "Magnum Clips"
, "Uzi Clips"
, "Small Medi Pack"
, "Large Medi Pack"
, "Puzzle"
, "Key"
, "Lead Bar"
, "Scion"
};
namespace UI {
IGame *game;
float width;
@@ -48,6 +167,13 @@ namespace UI {
#define MAX_CHARS DYN_MESH_QUADS
enum BarType {
BAR_HEALTH,
BAR_OXYGEN,
BAR_OPTION,
BAR_MAX,
};
struct {
Vertex vertices[MAX_CHARS * 4];
Index indices[MAX_CHARS * 6];
@@ -129,13 +255,36 @@ namespace UI {
}
}
void textOut(const vec2 &pos, StringID str, Align align = aLeft, float width = 0) {
textOut(pos, STR[str], align, width);
}
#undef MAX_CHARS
/*
Texture *texInv, *texAction;
Texture* loadRAW(int width, int height, const char *name) {
FILE *f = fopen(name, "rb");
ASSERT(f);
uint8 *data = new uint8[width * height * 4];
fread(data, 1, width * height * 4, f);
fclose(f);
Texture *tex = new Texture(width, height, Texture::RGBA, false, data);
delete[] data;
return tex;
}
*/
void init(IGame *game) {
UI::game = game;
showHelp = false;
helpTipTime = 5.0f;
// texInv = loadRAW(64, 64, "btn_inv.raw");
// texAction = loadRAW(64, 64, "btn_action.raw");
}
void free() {
// delete texInv;
// delete texAction;
}
void update() {
@@ -185,48 +334,23 @@ namespace UI {
Core::setDepthTest(true);
}
void renderBar(int type, const vec2 &pos, const vec2 &size, float value) {
void renderBar(BarType type, const vec2 &pos, const vec2 &size, float value, uint32 fgColor = 0xFFFFFFFF, uint32 bgColor = 0x80000000, uint32 brColor1 = 0xFF4C504C, uint32 brColor2 = 0xFF748474) {
MeshBuilder *mesh = game->getMesh();
mesh->addFrame(buffer.indices, buffer.vertices, buffer.iCount, buffer.vCount, pos - 2.0f, size + 4.0f, 0xFF4C504C, 0xFF748474);
mesh->addBar(buffer.indices, buffer.vertices, buffer.iCount, buffer.vCount, type, pos - 1.0f, size + 2.0f, 0x80000000);
if (value > 0.0f)
mesh->addBar(buffer.indices, buffer.vertices, buffer.iCount, buffer.vCount, type, pos, vec2(size.x * value, size.y), 0xFFFFFFFF);
if (brColor1 != 0 || brColor2 != 0)
mesh->addFrame(buffer.indices, buffer.vertices, buffer.iCount, buffer.vCount, pos - 2.0f, size + 4.0f, brColor1, brColor2);
if (bgColor != 0)
mesh->addBar(buffer.indices, buffer.vertices, buffer.iCount, buffer.vCount, whiteTile, pos - 1.0f, size + 2.0f, bgColor);
if (fgColor != 0 && value > 0.0f)
mesh->addBar(buffer.indices, buffer.vertices, buffer.iCount, buffer.vCount, barTile[type], pos, vec2(size.x * value, size.y), fgColor);
}
const char *helpText =
"Controls gamepad, touch and keyboard:@"
" H - Show or hide this help@"
" TAB - Inventory@"
" LEFT - Left@"
" RIGHT - Right@"
" UP - Run@"
" DOWN - Back@"
" SHIFT - Walk@"
" SPACE - Draw Weapon@"
" CTRL - Action@"
" D - Jump@"
" Z - Step Left@"
" X - Step Right@"
" A - Roll@"
" C - Look # not implemented #@"
" V - First Person View@"
" R - slow motion@"
" T - fast motion@"
" ALT + ENTER - Fullscreen@@"
"Actions:@"
" Out of water - Run + Action@"
" Handstand - Run + Walk@"
" Swan dive - Run + Walk + jump@"
" DOZY on - Look + Step Right + Action + Jump@"
" DOZY off - Walk@";
void renderHelp() {
if (showHelp)
textOut(vec2(0, 64), helpText, aRight, width - 32);
textOut(vec2(0, 64), STR_HELP_TEXT, aRight, width - 32);
else
if (helpTipTime > 0.0f)
textOut(vec2(0, 480 - 32), "Press H for help", aCenter, width);
textOut(vec2(0, 480 - 32), STR_HELP_PRESS, aCenter, width);
}
};

View File

@@ -13,7 +13,7 @@
#define debugBreak() _asm { int 3 }
#endif
#define ASSERT(expr) if (expr) {} else { LOG("ASSERT %s in %s:%d\n", #expr, __FILE__, __LINE__); debugBreak(); }
#define ASSERT(expr) if (expr) {} else { LOG("ASSERT:\n %s:%d\n %s => %s\n", __FILE__, __LINE__, __FUNCTION__, #expr); debugBreak(); }
#ifndef ANDROID
#define LOG(...) printf(__VA_ARGS__)
@@ -21,11 +21,12 @@
#else
#define ASSERT(expr)
// #ifdef PROFILE
#ifdef LINUX
#define LOG(...) printf(__VA_ARGS__); fflush(stdout)
#else
#define LOG(...) printf(__VA_ARGS__)
// #else
// #define LOG(...) 0
// #endif
// #define LOG(...) 0
#endif
#endif
#ifdef ANDROID
@@ -727,9 +728,15 @@ struct short3 {
struct short4 {
int16 x, y, z, w;
short4() {}
short4(int16 x, int16 y, int16 z, int16 w) : x(x), y(y), z(z), w(w) {}
operator vec3() const { return vec3((float)x, (float)y, (float)z); };
operator short3() const { return *((short3*)this); }
inline bool operator == (const short4 &v) const { return x == v.x && y == v.y && z == v.z && w == v.w; }
inline bool operator != (const short4 &v) const { return !(*this == v); }
inline int16& operator [] (int index) const { ASSERT(index >= 0 && index <= 3); return ((int16*)this)[index]; }
};
@@ -960,14 +967,19 @@ struct Stream {
static char cacheDir[255];
static char contentDir[255];
typedef void (Callback)(Stream *stream, void *userData);
Callback *callback;
void *userData;
FILE *f;
const char *data;
char *data;
int size, pos;
char *name;
Stream(const void *data, int size) : f(NULL), data((char*)data), size(size), pos(0) {}
Stream(const void *data, int size) : callback(NULL), userData(NULL), f(NULL), data((char*)data), size(size), pos(0), name(NULL) {}
Stream(const char *name) : data(NULL), size(-1), pos(0) {
if (contentDir[0]) {
Stream(const char *name, Callback *callback = NULL, void *userData = NULL) : callback(callback), userData(userData), data(NULL), size(-1), pos(0), name(NULL) {
if (contentDir[0] && (!cacheDir[0] || !strstr(name, cacheDir))) {
char path[255];
path[0] = 0;
strcat(path, contentDir);
@@ -976,15 +988,35 @@ struct Stream {
} else
f = fopen(name, "rb");
if (!f) LOG("error loading file \"%s\"\n", name);
ASSERT(f != NULL);
if (!f) {
#ifdef __EMSCRIPTEN__
this->name = new char[64];
strcpy(this->name, name);
fseek(f, 0, SEEK_END);
size = ftell(f);
fseek(f, 0, SEEK_SET);
extern void osDownload(Stream *stream);
osDownload(this);
return;
#else
LOG("error loading file \"%s\"\n", name);
if (callback) {
callback(NULL, userData);
return;
} else {
ASSERT(false);
}
#endif
} else {
fseek(f, 0, SEEK_END);
size = ftell(f);
fseek(f, 0, SEEK_SET);
if (callback)
callback(this, userData);
}
}
~Stream() {
delete[] name;
if (f) fclose(f);
}