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

#23 split rooms geometry by 64k vertices; #15 TR2 cutscenes support;

This commit is contained in:
XProger
2017-11-21 13:14:13 +03:00
parent a8e7656a78
commit 8ca1e621cf
10 changed files with 422 additions and 241 deletions

View File

@@ -184,7 +184,7 @@ struct Camera : ICamera {
int indexB = min((indexA + 1), level->cameraFramesCount - 1);
if (indexA == level->cameraFramesCount - 1) {
if (level->cutEntity != -1)
if (level->isCutsceneLevel())
game->loadNextLevel();
else {
Character *lara = (Character*)game->getLara();
@@ -372,8 +372,8 @@ struct Camera : ICamera {
advTimer = 0.0f;
fov = firstPerson ? 90.0f : 65.0f;
znear = firstPerson ? 8.0f : 128.0f;
zfar = 40.0f * 1024.0f * 1024.0f;
znear = firstPerson ? 8.0f : 32.0f;
zfar = 40.0f * 1024.0f;
}
};

View File

@@ -371,6 +371,10 @@ struct Controller {
info.lava = true;
break;
case TR::FloorData::CLIMB :
info.climb = (*fd++).data; // climb mask
break;
default : LOG("unknown func: %d\n", cmd.func);
}
@@ -771,14 +775,21 @@ struct Controller {
virtual void cmdEmpty() {}
virtual void cmdEffect(int fx) {
if (fx == 18) return; // TR2 TODO MESH_SWAP1
if (fx == 19) return; // TR2 TODO MESH_SWAP2
if (fx == 20) return; // TR2 TODO MESH_SWAP3
if (fx == 21) flags.invisible = true; return; // TR2 TODO INV_ON visible = false
if (fx == 22) flags.invisible = false; return; // TR2 TODO INV_OFF visible = true
if (fx == 23) return; // TR2 TODO DYN_ON
if (fx == 24) return; // TR2 TODO DYN_OFF
ASSERT(false); // not implemented
switch (fx) {
case TR::Effect::MESH_SWAP_1 :
case TR::Effect::MESH_SWAP_2 :
case TR::Effect::MESH_SWAP_3 : {
if (!layers) initMeshOverrides();
uint32 mask = (layers[1].mask == 0xFFFFFFFF) ? 0 : 0xFFFFFFFF;
meshSwap(1, level->extra.meshSwap[fx - TR::Effect::MESH_SWAP_1], mask);
break;
}
case TR::Effect::INV_ON : flags.invisible = true; break;
case TR::Effect::INV_OFF : flags.invisible = false; break;
case TR::Effect::DYN_ON : break; // TODO TR2
case TR::Effect::DYN_OFF : break; // TODO TR2
default : ASSERT(false);
}
}
virtual void updateAnimation(bool commands) {
@@ -896,22 +907,31 @@ struct Controller {
}
void updateLights(bool lerp = true) {
TR::Room::Light sunLight;
if (getModel()) {
const TR::Room &room = getRoom();
vec3 center = getBoundingBox().center();
float maxAtt = 0.0f;
for (int i = 0; i < room.lightsCount; i++) {
TR::Room::Light &light = room.lights[i];
if (light.intensity > 0x1FFF) continue;
if (room.flags.sky) { // TODO trace rooms up for sun light, add direct light projection
sunLight.x = int32(center.x);
sunLight.y = int32(center.y) - 8192;
sunLight.z = int32(center.z);
targetLight = &sunLight;
} else {
for (int i = 0; i < room.lightsCount; i++) {
TR::Room::Light &light = room.lights[i];
if (light.intensity > 0x1FFF) continue;
vec3 dir = vec3(float(light.x), float(light.y), float(light.z)) - center;
float att = max(0.0f, 1.0f - dir.length2() / float(light.radius) / float(light.radius)) * (1.0f - intensityf(light.intensity));
vec3 dir = vec3(float(light.x), float(light.y), float(light.z)) - center;
float att = max(0.0f, 1.0f - dir.length2() / float(light.radius) / float(light.radius)) * (1.0f - intensityf(light.intensity));
if (att > maxAtt) {
maxAtt = att;
targetLight = &light;
if (att > maxAtt) {
maxAtt = att;
targetLight = &light;
}
}
}
} else

View File

@@ -1463,4 +1463,28 @@ struct Natla : Human {
Natla(IGame *game, int entity) : Human(game, entity, 400) {}
};
struct Dog : Enemy {
enum {
ANIM_SLEEP = 5,
ANIM_DEATH = 13,
};
enum {
STATE_DEATH = 10,
};
Dog(IGame *game, int entity) : Enemy(game, entity, 6, 10, 0.0f, 0.0f) {
jointChest = 19;
jointHead = 20;
animation.setAnim(ANIM_SLEEP);
}
virtual int getStateDeath() {
if (state != STATE_DEATH)
return animation.setAnim(ANIM_DEATH);
return state;
}
};
#endif

View File

@@ -473,14 +473,24 @@
namespace TR {
enum Version : uint32 {
VER_UNKNOWN = 0,
VER_TR1_PC = 1,
VER_TR1_PSX = 2,
VER_TR1_SAT = 4,
VER_TR1 = VER_TR1_PC | VER_TR1_PSX | VER_TR1_SAT,
VER_TR2_PC = 16,
VER_TR2_PSX = 32,
VER_TR2 = VER_TR2_PC | VER_TR2_PSX,
VER_UNKNOWN = 0,
VER_PC = 256,
VER_PSX = 512,
VER_SEGA = 1024,
VER_TR1 = 1,
VER_TR2 = 2,
VER_TR3 = 4,
VER_TR4 = 8,
VER_TR5 = 16,
VER_TR1_PC = VER_TR1 | VER_PC,
VER_TR1_PSX = VER_TR1 | VER_PSX,
VER_TR1_SEGA = VER_TR1 | VER_SEGA,
VER_TR2_PC = VER_TR2 | VER_PC,
VER_TR2_PSX = VER_TR2 | VER_PSX,
};
enum {
@@ -518,8 +528,16 @@ namespace TR {
LARA_HANDSFREE ,
FLIP_MAP ,
DRAW_RIGHTGUN ,
UNK5 ,
DRAW_LEFTGUN ,
FLICKER ,
UNKNOWN ,
MESH_SWAP_1 ,
MESH_SWAP_2 ,
MESH_SWAP_3 ,
INV_ON ,
INV_OFF ,
DYN_ON ,
DYN_OFF ,
};
enum {
@@ -814,7 +832,7 @@ namespace TR {
uint16 meshesCount;
int16 alternateRoom;
struct {
uint16 water:1, unused:14, visible:1;
uint16 water:1, :2, sky:1, :1, wind:1, unused:9, visible:1;
} flags;
struct Portal {
@@ -896,6 +914,7 @@ namespace TR {
CEILING ,
TRIGGER ,
LAVA ,
CLIMB ,
};
};
@@ -1082,19 +1101,26 @@ namespace TR {
bool isEnemy() const {
return (type >= ENEMY_DOPPELGANGER && type <= ENEMY_GIANT_MUTANT) || type == SCION_TARGET;
return (type >= ENEMY_DOPPELGANGER && type <= ENEMY_GIANT_MUTANT) || type == SCION_TARGET ||
(type >= ENEMY_DOG && type <= ENEMY_DRAGON_BACK) ||
(type >= ENEMY_SHARK && type <= ENEMY_MONK_2);
}
bool isBigEnemy() const {
return type == ENEMY_REX || type == ENEMY_MUTANT_1 || type == ENEMY_CENTAUR;
}
bool isVehicle() const {
return type == VEHICLE_BOAT || type == VEHICLE_SNOWMOBILE_RED || type == VEHICLE_SNOWMOBILE_BLACK;
}
bool isDoor() const {
return type >= DOOR_1 && type <= DOOR_6;
}
bool isCollider() const {
return isEnemy() ||
isVehicle() ||
isDoor() ||
(type == DRAWBRIDGE && flags.active != ACTIVE) ||
((type == HAMMER_HANDLE || type == HAMMER_BLOCK) && flags.collision) ||
@@ -1137,7 +1163,7 @@ namespace TR {
}
bool castShadow() const {
return isLara() || isEnemy() || isActor() || type == DART || type == TRAP_SWORD;
return isLara() || isEnemy() || isVehicle() || isActor() || type == DART || type == TRAP_SWORD;
}
void getAxis(int &dx, int &dz) {
@@ -1573,10 +1599,10 @@ namespace TR {
{ "BOAT" , "Venice", NO_TRACK },
{ "VENICE" , "Bartoli's Hideout", NO_TRACK },
{ "OPERA" , "Opera House", NO_TRACK },
{ "CUT2" , "", 3 },
{ "CUT2" , "", 4 },
{ "RIG" , "Offshore Rig", NO_TRACK },
{ "PLATFORM" , "Diving Area", NO_TRACK },
{ "CUT3" , "", 4 },
{ "CUT3" , "", 5 },
{ "UNWATER" , "40 Fathoms", NO_TRACK },
{ "KEEL" , "Wreck of the Maria Doria", NO_TRACK },
{ "LIVING" , "Living Quarters", NO_TRACK },
@@ -1587,7 +1613,7 @@ namespace TR {
{ "ICECAVE" , "Ice Palace", NO_TRACK },
{ "EMPRTOMB" , "Temple of Xian", NO_TRACK },
{ "FLOATING" , "Floating Islands", NO_TRACK },
{ "CUT4" , "", 2 },
{ "CUT4" , "", 30 },
{ "XIAN" , "The Dragon's Lair", NO_TRACK },
{ "HOUSE" , "Home Sweet Home", NO_TRACK },
};
@@ -1783,6 +1809,7 @@ namespace TR {
};
struct FloorInfo {
// TODO reduse type sizes
float roomFloor, roomCeiling;
int roomNext, roomBelow, roomAbove;
float floor, ceiling;
@@ -1791,6 +1818,7 @@ namespace TR {
int boxIndex;
int lava;
int trigCmdCount;
int climb;
Trigger trigger;
FloorData::TriggerInfo trigInfo;
FloorData::TriggerCommand trigCmd[MAX_TRIGGER_COMMANDS];
@@ -1822,6 +1850,8 @@ namespace TR {
int16 puzzleDone[4];
int16 weapons[4];
int16 braid;
int16 laraSpec;
int16 meshSwap[3];
int16 sky;
int16 smoke;
int16 glyphs;
@@ -1831,6 +1861,7 @@ namespace TR {
int16 passport_closed;
int16 map;
int16 compass;
int16 stopwatch;
int16 home;
int16 detail;
int16 sound;
@@ -1868,7 +1899,7 @@ namespace TR {
id = getLevelID(stream.size);
if (version == VER_UNKNOWN) {
if (version == VER_UNKNOWN || version == VER_TR1_PSX) {
stream.read(magic);
if (magic != MAGIC_TR1_PC && magic != MAGIC_TR2_PC) {
soundOffset = magic;
@@ -1879,7 +1910,7 @@ namespace TR {
case MAGIC_TR1_PC : version = VER_TR1_PC; break;
case MAGIC_TR1_PSX : version = VER_TR1_PSX; break;
case MAGIC_TR2_PC : version = VER_TR2_PC; break;
default : version = VER_UNKNOWN;
default : ;
}
}
@@ -1907,7 +1938,7 @@ namespace TR {
stream.read(palette32, 256);
}
if (version == VER_TR1_PSX) {
if (version == VER_TR1_PSX && !isCutsceneLevel()) {
uint32 offsetTexTiles;
stream.seek(8);
stream.read(offsetTexTiles);
@@ -1938,8 +1969,7 @@ namespace TR {
if (version == VER_TR2_PC)
stream.read(tiles16, tilesCount);
if (!version /*PSX cutscene*/ || version == VER_TR1_PSX) {
version = VER_TR1_PSX;
if (version == VER_TR1_PSX) {
// tiles
stream.read(tiles4, tilesCount = 13);
stream.read(cluts, clutsCount = 512);
@@ -1949,146 +1979,8 @@ namespace TR {
// rooms
rooms = new Room[stream.read(roomsCount)];
for (int i = 0; i < roomsCount; i++) {
Room &r = rooms[i];
Room::Data &d = r.data;
// room info
stream.read(r.info);
// room data
stream.read(d.size);
int startOffset = stream.pos;
if (version == VER_TR1_PSX) stream.seek(2);
d.vertices = stream.read(d.vCount) ? new Room::Data::Vertex[d.vCount] : NULL;
for (int i = 0; i < d.vCount; i++) {
Room::Data::Vertex &v = d.vertices[i];
if (version == VER_TR2_PSX) {
union Compressed {
struct { uint32 lighting:8, attributes:8, z:5, y:5, x:5, w:1; };
uint32 value;
} comp;
stream.read(comp.value);
v.vertex.x = (comp.x << 10);
v.vertex.y = (comp.y << 8) + r.info.yTop;
v.vertex.z = (comp.z << 10);
v.lighting = comp.lighting;
v.attributes = comp.attributes;
ASSERT(comp.w == 0);
} else {
stream.read(v.vertex.x);
stream.read(v.vertex.y);
stream.read(v.vertex.z);
stream.read(v.lighting);
if (version == VER_TR2_PC) {
stream.read(v.attributes);
stream.read(v.lighting2);
}
}
}
if (version == VER_TR2_PSX)
stream.seek(2);
if (version == VER_TR1_PSX || version == VER_TR2_PSX)
for (int j = 0; j < d.vCount; j++) // convert vertex luminance from PSX to PC format
d.vertices[j].lighting = 0x1FFF - (d.vertices[j].lighting << 5);
d.rectangles = stream.read(d.rCount) ? new Rectangle[d.rCount] : NULL;
if (version == VER_TR2_PSX) {
for (int i = 0; i < d.rCount; i++)
stream.raw(&d.rectangles[i].flags.value, sizeof(uint16)); // hack to read texture:15 color:1
if ((stream.pos - startOffset) % 4) stream.seek(2);
for (int i = 0; i < d.rCount; i++) {
stream.raw(d.rectangles[i].vertices, sizeof(d.rectangles[i].vertices));
d.rectangles[i].vertices[0] >>= 2;
d.rectangles[i].vertices[1] >>= 2;
d.rectangles[i].vertices[2] >>= 2;
d.rectangles[i].vertices[3] >>= 2;
}
} else
stream.raw(d.rectangles, sizeof(Rectangle) * d.rCount);
if (version == VER_TR1_PSX || version == VER_TR2_PSX)
for (int j = 0; j < d.rCount; j++) // swap indices (quad strip -> quad list)
swap(d.rectangles[j].vertices[2], d.rectangles[j].vertices[3]);
d.triangles = stream.read(d.tCount) ? new Triangle[d.tCount] : NULL;
if (version == VER_TR2_PSX) {
stream.seek(2);
for (int i = 0; i < d.tCount; i++) {
stream.raw(&d.triangles[i].flags.value, sizeof(uint16)); // hack to read texture:15 color:1
stream.raw(d.triangles[i].vertices, sizeof(d.triangles[i].vertices));
d.triangles[i].vertices[0] >>= 2;
d.triangles[i].vertices[1] >>= 2;
d.triangles[i].vertices[2] >>= 2;
}
} else
stream.raw(d.triangles, sizeof(Triangle) * d.tCount);
// room sprites
if (version == VER_TR2_PSX) { // there is no room sprites
d.sprites = NULL;
d.sCount = NULL;
} else
stream.read(d.sprites, stream.read(d.sCount));
// portals
stream.read(r.portals, stream.read(r.portalsCount));
// sectors
stream.read(r.zSectors);
stream.read(r.xSectors);
stream.read(r.sectors, r.zSectors * r.xSectors);
// ambient light luminance
stream.read(r.ambient);
if (version & VER_TR2) {
stream.read(r.ambient2);
stream.read(r.lightMode);
}
// lights
r.lights = stream.read(r.lightsCount) ? new Room::Light[r.lightsCount] : NULL;
for (int i = 0; i < r.lightsCount; i++) {
Room::Light &light = r.lights[i];
stream.read(light.x);
stream.read(light.y);
stream.read(light.z);
if (version == VER_TR1_PSX) {
uint32 intensity;
light.intensity = stream.read(intensity);
} else
stream.read(light.intensity);
if (version & VER_TR2)
stream.read(light.intensity2);
stream.read(light.radius);
if (version & VER_TR2)
stream.read(light.radius2);
light.radius *= 2;
}
// meshes
stream.read(r.meshesCount);
r.meshes = r.meshesCount ? new Room::Mesh[r.meshesCount] : NULL;
for (int i = 0; i < r.meshesCount; i++) {
Room::Mesh &m = r.meshes[i];
stream.read(m.x);
stream.read(m.y);
stream.read(m.z);
stream.read(m.rotation);
stream.read(m.intensity);
if (version & VER_TR2)
stream.read(m.intensity2);
stream.read(m.meshID);
if (version == VER_TR1_PSX)
stream.seek(2); // just an align for PSX version
}
// misc flags
stream.read(r.alternateRoom);
stream.read(r.flags);
}
for (int i = 0; i < roomsCount; i++)
readRoom(stream, rooms[i]);
// floors
stream.read(floors, stream.read(floorsCount));
@@ -2113,7 +2005,7 @@ namespace TR {
stream.read(m.node);
stream.read(m.frame);
stream.read(m.animation);
if (version == VER_TR1_PSX || version == VER_TR2_PSX)
if (version & VER_PSX)
stream.seek(2);
m.type = Entity::remap(version, m.type);
}
@@ -2139,7 +2031,7 @@ namespace TR {
boxes = stream.read(boxesCount) ? new Box[boxesCount] : NULL;
for (int i = 0; i < boxesCount; i++) {
Box &b = boxes[i];
if (version == VER_TR1_PC || version == VER_TR1_PSX) {
if (version & VER_TR1) {
stream.read(b.minZ);
stream.read(b.maxZ);
stream.read(b.minX);
@@ -2162,14 +2054,14 @@ namespace TR {
for (int i = 0; i < 2; i++) {
stream.read(zones[i].ground1, boxesCount);
stream.read(zones[i].ground2, boxesCount);
if (version & VER_TR2) {
if (!(version & VER_TR1)) {
stream.read(zones[i].ground3, boxesCount);
stream.read(zones[i].ground4, boxesCount);
} else {
zones[i].ground3 = NULL;
zones[i].ground4 = NULL;
}
stream.read(zones[i].fly, boxesCount);
stream.read(zones[i].fly, boxesCount);
}
// animated textures
stream.read(animTexturesData, stream.read(animTexturesDataSize));
@@ -2200,7 +2092,8 @@ namespace TR {
if (isCutsceneLevel()) {
for (int i = 0; i < entitiesBaseCount; i++) {
TR::Entity &e = entities[i];
if (e.isActor()) {
if ((((version & VER_TR1)) && e.isActor()) ||
(((version & VER_TR2)) && e.isLara())) {
cutEntity = i;
break;
}
@@ -2251,7 +2144,7 @@ namespace TR {
}
// cinematic frames for cameras (PSX)
if (version == VER_TR1_PSX || version == VER_TR2_PSX) {
if (version & VER_PSX) {
stream.seek(4);
stream.read(cameraFrames, stream.read(cameraFramesCount));
}
@@ -2329,68 +2222,68 @@ namespace TR {
isHomeLevel = false;
switch (size) {
// TR1
case 508614 :
case 508614 : version = VER_TR1_PSX;
case 316138 :
case 316460 : return LVL_TR1_TITLE;
case 1074234 :
case 1074234 : version = VER_TR1_PSX;
case 3236806 :
case 3237128 : isHomeLevel = true; return LVL_TR1_GYM;
case 1448896 :
case 1448896 : version = VER_TR1_PSX;
case 2533312 :
case 2533634 : return LVL_TR1_1;
case 2873406 : isDemoLevel = true; return LVL_TR1_2;
case 1535734 :
case 1535734 : version = VER_TR1_PSX;
case 2873128 :
case 2873450 : return LVL_TR1_2;
case 1630560 :
case 1630560 : version = VER_TR1_PSX;
case 2934408 :
case 2934730 : return LVL_TR1_3A;
case 1506614 :
case 1506614 : version = VER_TR1_PSX;
case 2737936 :
case 2738258 : return LVL_TR1_3B;
case 722402 :
case 722402 : version = VER_TR1_PSX;
case 599840 : return LVL_TR1_CUT_1;
case 1621970 :
case 1621970 : version = VER_TR1_PSX;
case 3030550 :
case 3030872 : return LVL_TR1_4;
case 1585942 :
case 1585942 : version = VER_TR1_PSX;
case 2718218 :
case 2718540 : return LVL_TR1_5;
case 1708464 :
case 1708464 : version = VER_TR1_PSX;
case 3139590 :
case 3074376 : return LVL_TR1_6;
case 1696664 :
case 1696664 : version = VER_TR1_PSX;
case 2817290 :
case 2817612 : return LVL_TR1_7A;
case 1733274 :
case 1733274 : version = VER_TR1_PSX;
case 3388774 :
case 3389096 : return LVL_TR1_7B;
case 542960 :
case 542960 : version = VER_TR1_PSX;
case 354320 : return LVL_TR1_CUT_2;
case 1563356 :
case 1563356 : version = VER_TR1_PSX;
case 2880242 :
case 2880564 : return LVL_TR1_8A;
case 1565630 :
case 1565630 : version = VER_TR1_PSX;
case 2886434 :
case 2886756 : return LVL_TR1_8B;
case 1619360 :
case 1619360 : version = VER_TR1_PSX;
case 3105128 :
case 3105450 : return LVL_TR1_8C;
case 1678018 :
case 1678018 : version = VER_TR1_PSX;
case 3223816 :
case 3224138 : return LVL_TR1_10A;
case 636660 :
case 636660 : version = VER_TR1_PSX;
case 512104 : return LVL_TR1_CUT_3;
case 1686748 :
case 1686748 : version = VER_TR1_PSX;
case 3094020 : return LVL_TR1_10B;
case 940398 :
case 940398 : version = VER_TR1_PSX;
case 879582 : return LVL_TR1_CUT_4;
case 1814278 :
case 1814278 : version = VER_TR1_PSX;
case 3531702 :
case 3532024 : return LVL_TR1_10C;
case 3278614 :
case 3278614 : version = VER_TR1_PSX;
case 3279242 : return LVL_TR1_EGYPT;
case 3270370 :
case 3270370 : version = VER_TR1_PSX;
case 3270998 : return LVL_TR1_CAT;
case 3208018 : return LVL_TR1_END;
case 3153300 : return LVL_TR1_END2;
@@ -2454,13 +2347,11 @@ namespace TR {
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61
};
switch (version) {
case VER_TR2_PC :
ASSERT(track < COUNT(TR2_TRACK_MAPPING));
return TR2_TRACK_MAPPING[track];
default :
return track;
if (version & VER_TR2) {
ASSERT(track < COUNT(TR2_TRACK_MAPPING));
return TR2_TRACK_MAPPING[track];
}
return track;
}
void initExtra() {
@@ -2479,11 +2370,16 @@ namespace TR {
case Entity::LARA_MAGNUMS : extra.weapons[2] = i; break;
case Entity::LARA_UZIS : extra.weapons[3] = i; break;
case Entity::LARA_BRAID : extra.braid = i; break;
case Entity::LARA_SPEC : extra.laraSpec = i; break;
case Entity::CUT_1 : extra.meshSwap[0] = i; break;
case Entity::CUT_2 : extra.meshSwap[1] = i; break;
case Entity::CUT_3 : extra.meshSwap[2] = i; break;
case Entity::INV_PASSPORT : extra.inv.passport = i; break;
case Entity::INV_PASSPORT_CLOSED : extra.inv.passport_closed = i; break;
case Entity::INV_MAP : extra.inv.map = i; break;
case Entity::INV_COMPASS : extra.inv.compass = i; break;
case Entity::INV_STOPWATCH : extra.inv.stopwatch = i; break;
case Entity::INV_HOME : extra.inv.home = i; break;
case Entity::INV_DETAIL : extra.inv.detail = i; break;
case Entity::INV_SOUND : extra.inv.sound = i; break;
@@ -2551,7 +2447,9 @@ namespace TR {
cutMatrix.translate(vec3(float(e.x), float(e.y), float(e.z)));
cutMatrix.rotateY(PI * 0.5f);
break;
default : cutMatrix.translate(vec3(float(e.x), float(e.y), float(e.z)));;
default :
cutMatrix.translate(vec3(float(e.x), float(e.y), float(e.z)));
cutMatrix.rotateY(e.rotation);
}
}
}
@@ -2559,6 +2457,7 @@ namespace TR {
LevelID titleId() {
if (version & VER_TR1) return LVL_TR1_TITLE;
if (version & VER_TR2) return LVL_TR2_TITLE;
return LVL_TR1_TITLE;
ASSERT(false);
}
@@ -2571,6 +2470,155 @@ namespace TR {
id == LVL_TR2_CUT_1 || id == LVL_TR2_CUT_2 || id == LVL_TR2_CUT_3 || id == LVL_TR2_CUT_4;
}
void readRoom(Stream &stream, Room &r) {
Room::Data &d = r.data;
// room info
stream.read(r.info);
// room data
stream.read(d.size);
int startOffset = stream.pos;
if (version == VER_TR1_PSX) stream.seek(2);
d.vertices = stream.read(d.vCount) ? new Room::Data::Vertex[d.vCount] : NULL;
for (int i = 0; i < d.vCount; i++) {
Room::Data::Vertex &v = d.vertices[i];
if (version == VER_TR2_PSX) {
union Compressed {
struct { uint32 lighting:8, attributes:8, z:5, y:5, x:5, w:1; };
uint32 value;
} comp;
stream.read(comp.value);
v.vertex.x = (comp.x << 10);
v.vertex.y = (comp.y << 8) + r.info.yTop;
v.vertex.z = (comp.z << 10);
v.lighting = comp.lighting;
v.attributes = comp.attributes;
ASSERT(comp.w == 0);
} else {
stream.read(v.vertex.x);
stream.read(v.vertex.y);
stream.read(v.vertex.z);
stream.read(v.lighting);
if (version == VER_TR2_PC) {
stream.read(v.attributes);
stream.read(v.lighting2);
}
}
if (version & VER_PSX)
v.lighting = 0x1FFF - (v.lighting << 5); // convert vertex luminance from PSX to PC format
}
if (version == VER_TR2_PSX)
stream.seek(2);
d.rectangles = stream.read(d.rCount) ? new Rectangle[d.rCount] : NULL;
if (version == VER_TR2_PSX) {
for (int i = 0; i < d.rCount; i++)
stream.raw(&d.rectangles[i].flags.value, sizeof(uint16)); // hack to read texture:15 color:1
if ((stream.pos - startOffset) % 4) stream.seek(2);
for (int i = 0; i < d.rCount; i++) {
stream.raw(d.rectangles[i].vertices, sizeof(d.rectangles[i].vertices));
d.rectangles[i].vertices[0] >>= 2;
d.rectangles[i].vertices[1] >>= 2;
d.rectangles[i].vertices[2] >>= 2;
d.rectangles[i].vertices[3] >>= 2;
}
} else
stream.raw(d.rectangles, sizeof(Rectangle) * d.rCount);
if (version == VER_TR1_PSX || version == VER_TR2_PSX)
for (int j = 0; j < d.rCount; j++) // swap indices (quad strip -> quad list)
swap(d.rectangles[j].vertices[2], d.rectangles[j].vertices[3]);
d.triangles = stream.read(d.tCount) ? new Triangle[d.tCount] : NULL;
if (version == VER_TR2_PSX) {
stream.seek(2);
for (int i = 0; i < d.tCount; i++) {
stream.raw(&d.triangles[i].flags.value, sizeof(uint16)); // hack to read texture:15 color:1
stream.raw(d.triangles[i].vertices, sizeof(d.triangles[i].vertices));
d.triangles[i].vertices[0] >>= 2;
d.triangles[i].vertices[1] >>= 2;
d.triangles[i].vertices[2] >>= 2;
}
} else
stream.raw(d.triangles, sizeof(Triangle) * d.tCount);
// room sprites
if (version == VER_TR2_PSX) { // there is no room sprites
d.sprites = NULL;
d.sCount = NULL;
} else
stream.read(d.sprites, stream.read(d.sCount));
// portals
stream.read(r.portals, stream.read(r.portalsCount));
if (version == VER_TR2_PSX)
for (int i = 0; i < r.portalsCount; i++) {
r.portals[i].vertices[0].y += r.info.yTop;
r.portals[i].vertices[1].y += r.info.yTop;
r.portals[i].vertices[2].y += r.info.yTop;
r.portals[i].vertices[3].y += r.info.yTop;
}
// sectors
stream.read(r.zSectors);
stream.read(r.xSectors);
stream.read(r.sectors, r.zSectors * r.xSectors);
// ambient light luminance
stream.read(r.ambient);
if (version & VER_TR2) {
stream.read(r.ambient2);
stream.read(r.lightMode);
}
// lights
r.lights = stream.read(r.lightsCount) ? new Room::Light[r.lightsCount] : NULL;
for (int i = 0; i < r.lightsCount; i++) {
Room::Light &light = r.lights[i];
stream.read(light.x);
stream.read(light.y);
stream.read(light.z);
if (version == VER_TR1_PSX) {
uint32 intensity;
light.intensity = stream.read(intensity);
} else
stream.read(light.intensity);
if (version & VER_TR2)
stream.read(light.intensity2);
stream.read(light.radius);
if (version & VER_TR2)
stream.read(light.radius2);
light.radius *= 2;
}
// meshes
stream.read(r.meshesCount);
r.meshes = r.meshesCount ? new Room::Mesh[r.meshesCount] : NULL;
for (int i = 0; i < r.meshesCount; i++) {
Room::Mesh &m = r.meshes[i];
stream.read(m.x);
stream.read(m.y);
stream.read(m.z);
stream.read(m.rotation);
stream.read(m.intensity);
if (version & VER_TR2)
stream.read(m.intensity2);
stream.read(m.meshID);
if (version == VER_TR1_PSX)
stream.seek(2); // just an align for PSX version
}
// misc flags
stream.read(r.alternateRoom);
stream.read(r.flags);
}
void readMeshes(Stream &stream) {
uint32 meshDataSize;
stream.read(meshDataSize);
@@ -2689,9 +2737,8 @@ namespace TR {
}
}
uint16 crCount = 0, ctCount = 0, rCount = 0, tCount = 0;
if (version == VER_TR2_PSX && nCount > 0) {
if (version == VER_TR2_PSX && nCount > 0) { // TODO probably for unused meshes only but need to check
uint16 crCount = 0, ctCount = 0;
stream.read(crCount);
stream.seek((sizeof(Rectangle) + 2) * crCount);
stream.read(ctCount);
@@ -2700,8 +2747,9 @@ namespace TR {
stream.read(mesh.rectangles, stream.read(mesh.rCount));
stream.read(mesh.triangles, stream.read(mesh.tCount));
ASSERT(mesh.rCount != 0 || mesh.tCount != 0);
if (mesh.rCount != 0 || mesh.tCount != 0)
LOG("! warning: mesh %d has no geometry with %d vertices\n", meshesCount - 1, mesh.vCount);
//ASSERT(mesh.rCount != 0 || mesh.tCount != 0);
for (int i = 0; i < mesh.rCount; i++) {
if (mesh.rectangles[i].flags.texture < 256)
@@ -2728,6 +2776,7 @@ namespace TR {
break;
}
default : ASSERT(false);
}
#define RECALC_ZERO_NORMALS(mesh, face, count)\
@@ -2839,6 +2888,7 @@ namespace TR {
SET_PARAMS(t, d, d.clut);
break;
}
default : ASSERT(false);
}
}
@@ -2889,6 +2939,7 @@ namespace TR {
t.texCoord[1] = { d.u1, d.v1 };
break;
}
default : ASSERT(false);
}
}
@@ -2999,6 +3050,7 @@ namespace TR {
}
break;
}
default : ASSERT(false);
}
}
@@ -3017,6 +3069,7 @@ namespace TR {
CLUT &clut = cluts[t.clut];
return clut.color[part ? tile.index[idx].b : tile.index[idx].a];
}
default : ASSERT(false);
}
return Color32(255, 0, 255, 255);
}
@@ -3030,6 +3083,7 @@ namespace TR {
case VER_TR2_PC : size = FOURCC(data + 4) + 8; break; // read size from wave header
case VER_TR1_PSX :
case VER_TR2_PSX : size = soundSize[index]; break;
default : ASSERT(false);
}
return new Stream(data, size);
}

View File

@@ -55,6 +55,7 @@ struct Inventory {
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_STOPWATCH : desc = { STR_STOPWATCH, PAGE_INVENTORY, level->extra.inv.stopwatch }; 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;
@@ -89,7 +90,7 @@ struct Inventory {
default : desc = { STR_UNKNOWN, PAGE_ITEMS, -1 }; break;
}
if (desc.model > -1) {
if (desc.model > -1 && level->models[desc.model].animation != 0xFFFF) {
anim = new Animation(level, &level->models[desc.model]);
anim->isEnded = true;
} else
@@ -203,6 +204,7 @@ struct Inventory {
new Stream("level/TITLEH.PCX", loadTitleBG, this);
} else {
add(TR::Entity::INV_COMPASS);
add(TR::Entity::INV_STOPWATCH);
for (int i = 0; i < COUNT(background); i++)
background[i] = new Texture(INVENTORY_BG_SIZE, INVENTORY_BG_SIZE, Texture::RGBA, false);
@@ -647,6 +649,7 @@ struct Inventory {
case TR::Entity::INV_PASSPORT_CLOSED :
case TR::Entity::INV_MAP :
case TR::Entity::INV_COMPASS :
case TR::Entity::INV_STOPWATCH :
case TR::Entity::INV_HOME :
case TR::Entity::INV_DETAIL :
case TR::Entity::INV_SOUND :
@@ -894,7 +897,8 @@ struct Inventory {
renderPassport(item);
break;
case TR::Entity::INV_HOME :
case TR::Entity::INV_COMPASS :
case TR::Entity::INV_COMPASS :
case TR::Entity::INV_STOPWATCH :
case TR::Entity::INV_MAP :
break;
case TR::Entity::INV_DETAIL :

View File

@@ -500,7 +500,7 @@ struct Lara : Character {
dbgBoxes = NULL;
#endif
if (getEntity().isLara()) {
if (getEntity().isLara() && !level->isCutsceneLevel()) {
if (getRoom().flags.water)
animation.setAnim(ANIM_UNDERWATER);
else
@@ -1353,9 +1353,11 @@ struct Lara : Character {
}
void drawGun(int right) {
int mask = right ? BODY_ARM_R3 : BODY_ARM_L3; // unholster
int mask = (right ? BODY_ARM_R3 : BODY_ARM_L3); // unholster
if (layers[1].mask & mask)
mask = right ? BODY_LEG_R1 : BODY_LEG_L1; // holster
mask = (layers[1].mask & ~mask) | (right ? BODY_LEG_R1 : BODY_LEG_L1); // holster
else
mask |= layers[1].mask;
meshSwap(1, level->extra.weapons[wpnCurrent], mask);
}
@@ -1374,6 +1376,10 @@ struct Lara : Character {
case TR::Effect::LARA_BUBBLES : doBubbles(); break;
case TR::Effect::LARA_HANDSFREE : break;//meshSwap(1, level->extra.weapons[wpnCurrent], BODY_LEG_L1 | BODY_LEG_R1); break;
case TR::Effect::DRAW_RIGHTGUN : drawGun(true); break;
case TR::Effect::DRAW_LEFTGUN : drawGun(false); break;
case TR::Effect::MESH_SWAP_1 :
case TR::Effect::MESH_SWAP_2 :
case TR::Effect::MESH_SWAP_3 : Character::cmdEffect(fx);
case 26 : break; // TR2 TODO reset_hair
default : LOG("unknown effect command %d (anim %d)\n", fx, animation.index); ASSERT(false);
}

View File

@@ -80,8 +80,8 @@ struct Level : IGame {
virtual void loadNextLevel() {
#ifdef __EMSCRIPTEN__
if (level.id == TR::LEVEL_2 && level.version != TR::VER_TR1_PC) {
loadLevel(TR::TITLE);
if (level.id == TR::LVL_TR1_2 && level.version != TR::VER_TR1_PC) {
loadLevel(TR::LVL_TR1_TITLE);
return;
}
#endif
@@ -133,7 +133,7 @@ struct Level : IGame {
ptr += sizeof(*state);
}
Stream::write("savegame.dat", data, int(ptr - data));
//Stream::write("savegame.dat", data, int(ptr - data));
delete[] data;
LOG("Ok\n");
@@ -472,9 +472,10 @@ struct Level : IGame {
float pitch = b.flags.pitch ? (0.9f + randf() * 0.2f) : 1.0f;
if (!(flags & Sound::MUSIC)) {
switch (b.flags.mode) {
case 0 : flags |= Sound::UNIQUE; break;
case 0 : if (level.version & TR::VER_TR1) flags |= Sound::UNIQUE; break; // TODO check this
case 1 : flags |= Sound::REPLAY; break;
case 2 : flags |= Sound::FLIPPED | Sound::UNFLIPPED | Sound::LOOP; break;
case 2 : if (level.version & TR::VER_TR1) flags |= Sound::FLIPPED | Sound::UNFLIPPED | Sound::LOOP | Sound::UNIQUE; break;
case 3 : if (!(level.version & TR::VER_TR1)) flags |= Sound::FLIPPED | Sound::UNFLIPPED | Sound::LOOP | Sound::UNIQUE; break;
}
}
if (b.flags.gain) volume = max(0.0f, volume - randf() * 0.25f);
@@ -560,7 +561,7 @@ struct Level : IGame {
for (int i = 0; i < level.entitiesBaseCount; i++) {
TR::Entity &e = level.entities[i];
e.controller = initController(i);
if (e.type == TR::Entity::LARA || e.type == TR::Entity::CUT_1)
if (e.type == TR::Entity::LARA || ((level.version & TR::VER_TR1) && e.type == TR::Entity::CUT_1))
lara = (Lara*)e.controller;
}
@@ -684,7 +685,8 @@ struct Level : IGame {
case TR::Entity::CRYSTAL : return new Crystal(this, index);
case TR::Entity::TRAP_SWING_BLADE : return new TrapSwingBlade(this, index);
case TR::Entity::TRAP_SPIKES : return new TrapSpikes(this, index);
case TR::Entity::TRAP_BOULDER : return new TrapBoulder(this, index);
case TR::Entity::TRAP_BOULDER :
case TR::Entity::TRAP_BOULDERS : return new TrapBoulder(this, index);
case TR::Entity::DART : return new Dart(this, index);
case TR::Entity::TRAP_DART_EMITTER : return new TrapDartEmitter(this, index);
case TR::Entity::DRAWBRIDGE : return new Drawbridge(this, index);
@@ -732,7 +734,47 @@ struct Level : IGame {
case TR::Entity::EARTHQUAKE : return new Earthquake(this, index);
case TR::Entity::MUTANT_EGG_SMALL :
case TR::Entity::MUTANT_EGG_BIG : return new MutantEgg(this, index);
default : return (level.entities[index].modelIndex > 0) ? new Controller(this, index) : new Sprite(this, index, 0);
case TR::Entity::ENEMY_DOG : return new Dog(this, index);
case TR::Entity::ENEMY_GOON_MASK_1 :
case TR::Entity::ENEMY_GOON_MASK_2 :
case TR::Entity::ENEMY_GOON_MASK_3 :
case TR::Entity::ENEMY_GOON_KNIFE :
case TR::Entity::ENEMY_GOON_SHOTGUN :
case TR::Entity::ENEMY_RAT :
case TR::Entity::ENEMY_DRAGON_FRONT :
case TR::Entity::ENEMY_DRAGON_BACK :
case TR::Entity::ENEMY_SHARK :
case TR::Entity::ENEMY_MORAY_1 :
case TR::Entity::ENEMY_MORAY_2 :
case TR::Entity::ENEMY_BARACUDA :
case TR::Entity::ENEMY_DIVER :
case TR::Entity::ENEMY_GUNMAN_1 :
case TR::Entity::ENEMY_GUNMAN_2 :
case TR::Entity::ENEMY_GOON_STICK_1 :
case TR::Entity::ENEMY_GOON_STICK_2 :
case TR::Entity::ENEMY_GOON_FLAME :
case TR::Entity::UNUSED_23 :
case TR::Entity::ENEMY_SPIDER :
case TR::Entity::ENEMY_SPIDER_GIANT :
case TR::Entity::ENEMY_CROW :
case TR::Entity::ENEMY_TIGER :
case TR::Entity::ENEMY_MARCO :
case TR::Entity::ENEMY_GUARD_SPEAR :
case TR::Entity::ENEMY_GUARD_SPEAR_STATUE :
case TR::Entity::ENEMY_GUARD_SWORD :
case TR::Entity::ENEMY_GUARD_SWORD_STATUE :
case TR::Entity::ENEMY_YETI :
case TR::Entity::ENEMY_BIRD_MONSTER :
case TR::Entity::ENEMY_EAGLE :
case TR::Entity::ENEMY_MERCENARY_1 :
case TR::Entity::ENEMY_MERCENARY_2 :
case TR::Entity::ENEMY_MERCENARY_3 :
case TR::Entity::ENEMY_MERCENARY_SNOWMOBILE :
case TR::Entity::ENEMY_MONK_1 :
case TR::Entity::ENEMY_MONK_2 : return new Enemy(this, index, 100, 10, 0.0f, 0.0f);
default : return (level.entities[index].modelIndex > 0) ? new Controller(this, index) : new Sprite(this, index, 0);
}
}
@@ -1000,11 +1042,16 @@ struct Level : IGame {
void renderRooms(int *roomsList, int roomsCount) {
PROFILE_MARKER("ROOMS");
bool hasSky = false;
for (int i = 0; i < level.roomsCount; i++)
level.rooms[i].flags.visible = false;
for (int i = 0; i < roomsCount; i++)
level.rooms[roomsList[i]].flags.visible = true;
for (int i = 0; i < roomsCount; i++) {
TR::Room &r = level.rooms[roomsList[i]];
hasSky |= r.flags.sky;
r.flags.visible = true;
}
if (Core::pass == Core::passShadow)
return;
@@ -1031,7 +1078,8 @@ struct Level : IGame {
Core::setBlending(bmNone);
renderSky();
if (hasSky)
renderSky();
for (int transp = 0; transp < 2; transp++) {
for (int i = 0; i < roomsCount; i++) {
@@ -1395,7 +1443,8 @@ struct Level : IGame {
int roomsCount = 0;
getVisibleRooms(roomsList, roomsCount, TR::NO_ROOM, roomIndex, vec4(-1.0f, -1.0f, 1.0f, 1.0f), water);
/* // show all rooms
// show all rooms
/*
for (int i = 0; i < level.roomsCount; i++)
roomsList[i] = i;
roomsCount = level.roomsCount;

View File

@@ -90,6 +90,7 @@ struct Mesh {
void initRange(MeshRange &range) {
if (Core::support.VAO) {
ASSERT(aIndex < aCount);
range.aIndex = aIndex++;
range.bind(VAO);
bind(true);
@@ -158,6 +159,7 @@ struct MeshBuilder {
struct RoomRange {
MeshRange geometry[2]; // opaque & transparent
MeshRange sprites;
int split;
MeshRange **meshes;
} *rooms;
struct ModelRange {
@@ -192,10 +194,14 @@ struct MeshBuilder {
int iCount = 0, vCount = 0;
// get size of mesh for rooms (geometry & sprites)
int vStartRoom = vCount;
for (int i = 0; i < level.roomsCount; i++) {
TR::Room &r = level.rooms[i];
TR::Room::Data &d = r.data;
int vStartCount = vCount;
iCount += d.rCount * 6 + d.tCount * 3;
vCount += d.rCount * 4 + d.tCount * 3;
@@ -214,6 +220,12 @@ struct MeshBuilder {
iCount += d.sCount * 6;
vCount += d.sCount * 4;
if (vCount - vStartRoom > 0xFFFF) {
vStartRoom = vStartCount;
rooms[i].split = true;
} else
rooms[i].split = false;
}
// get models info
@@ -260,7 +272,7 @@ struct MeshBuilder {
int aCount = 0;
// build rooms
int vStartRoom = vCount;
vStartRoom = vCount;
aCount++;
for (int i = 0; i < level.roomsCount; i++) {
@@ -268,6 +280,11 @@ struct MeshBuilder {
TR::Room::Data &d = room.data;
RoomRange &range = rooms[i];
if (range.split) {
vStartRoom = vCount;
aCount++;
}
for (int transp = 0; transp < 2; transp++) { // opaque, opacity
range.geometry[transp].vStart = vStartRoom;
range.geometry[transp].iStart = iCount;
@@ -478,9 +495,13 @@ struct MeshBuilder {
// initialize Vertex Arrays
MeshRange rangeRoom;
rangeRoom.vStart = vStartRoom;
rangeRoom.vStart = 0;
mesh->initRange(rangeRoom);
for (int i = 0; i < level.roomsCount; i++) {
if (rooms[i].split) {
rangeRoom.vStart = rooms[i].geometry[0].vStart;
mesh->initRange(rangeRoom);
}
RoomRange &r = rooms[i];
r.geometry[0].aIndex = rangeRoom.aIndex;
r.geometry[1].aIndex = rangeRoom.aIndex;

View File

@@ -348,8 +348,9 @@ struct Atlas {
}
Texture* pack() {
width = nextPow2(int(sqrtf(float(size))));
height = (width * width / 2 > size) ? (width / 2) : width;
// TODO TR2 fix CUT2 AV
width = 2048;//nextPow2(int(sqrtf(float(size))));
height = 2048;//(width * width / 2 > size) ? (width / 2) : width;
// sort
int *indices = new int[tilesCount];
for (int i = 0; i < tilesCount; i++)

View File

@@ -24,6 +24,7 @@ enum StringID {
, STR_GAME
, STR_MAP
, STR_COMPASS
, STR_STOPWATCH
, STR_HOME
, STR_DETAIL
, STR_SOUND
@@ -114,6 +115,7 @@ const char *STR[STR_MAX] = {
, "Game"
, "Map"
, "Compass"
, "Statistics"
, "Lara's Home"
, "Detail Levels"
, "Sound"