From 8ca1e621cf03f5a57c391275fd13a681201ec6e7 Mon Sep 17 00:00:00 2001 From: XProger Date: Tue, 21 Nov 2017 13:14:13 +0300 Subject: [PATCH] #23 split rooms geometry by 64k vertices; #15 TR2 cutscenes support; --- src/camera.h | 6 +- src/controller.h | 52 ++++-- src/enemy.h | 24 +++ src/format.h | 456 ++++++++++++++++++++++++++--------------------- src/inventory.h | 8 +- src/lara.h | 12 +- src/level.h | 73 ++++++-- src/mesh.h | 25 ++- src/texture.h | 5 +- src/ui.h | 2 + 10 files changed, 422 insertions(+), 241 deletions(-) diff --git a/src/camera.h b/src/camera.h index c37e149..cb235aa 100644 --- a/src/camera.h +++ b/src/camera.h @@ -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; } }; diff --git a/src/controller.h b/src/controller.h index 787da01..dfed77c 100644 --- a/src/controller.h +++ b/src/controller.h @@ -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 diff --git a/src/enemy.h b/src/enemy.h index 29e85e7..b0ef638 100644 --- a/src/enemy.h +++ b/src/enemy.h @@ -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 \ No newline at end of file diff --git a/src/format.h b/src/format.h index 68ec23a..67a0f89 100644 --- a/src/format.h +++ b/src/format.h @@ -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); } diff --git a/src/inventory.h b/src/inventory.h index 3671d49..234973d 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -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 : diff --git a/src/lara.h b/src/lara.h index cc771aa..2fa6a63 100644 --- a/src/lara.h +++ b/src/lara.h @@ -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); } diff --git a/src/level.h b/src/level.h index 034d717..636caa5 100644 --- a/src/level.h +++ b/src/level.h @@ -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; diff --git a/src/mesh.h b/src/mesh.h index cd62ff5..30a72b0 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -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; diff --git a/src/texture.h b/src/texture.h index a384dd1..6fd613b 100644 --- a/src/texture.h +++ b/src/texture.h @@ -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++) diff --git a/src/ui.h b/src/ui.h index 4331e09..de845a5 100644 --- a/src/ui.h +++ b/src/ui.h @@ -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"