diff --git a/src/core.h b/src/core.h new file mode 100644 index 0000000..0ae2cd8 --- /dev/null +++ b/src/core.h @@ -0,0 +1,149 @@ +#ifndef H_CORE +#define H_CORE + +#include +#include +#include +#include + +#include "utils.h" +#include "input.h" + +#ifdef WIN32 + #if defined(_MSC_VER) // Visual Studio + #define GetProcOGL(x) *(void**)&x=(void*)wglGetProcAddress(#x); + #else // GCC + #define GetProcOGL(x) x=(typeof(x))wglGetProcAddress(#x); + #endif + +// Texture + PFNGLACTIVETEXTUREPROC glActiveTexture; + PFNGLCOMPRESSEDTEXIMAGE2DPROC glCompressedTexImage2D; +// Shader + PFNGLCREATEPROGRAMPROC glCreateProgram; + PFNGLDELETEPROGRAMPROC glDeleteProgram; + PFNGLLINKPROGRAMPROC glLinkProgram; + PFNGLUSEPROGRAMPROC glUseProgram; + PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog; + PFNGLCREATESHADERPROC glCreateShader; + PFNGLDELETESHADERPROC glDeleteShader; + PFNGLSHADERSOURCEPROC glShaderSource; + PFNGLATTACHSHADERPROC glAttachShader; + PFNGLCOMPILESHADERPROC glCompileShader; + PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog; + PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation; + PFNGLUNIFORM1IVPROC glUniform1iv; + PFNGLUNIFORM3FVPROC glUniform3fv; + PFNGLUNIFORM4FVPROC glUniform4fv; + PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv; + PFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation; + PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray; + PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray; + PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer; +// Mesh + PFNGLGENBUFFERSARBPROC glGenBuffers; + PFNGLDELETEBUFFERSARBPROC glDeleteBuffers; + PFNGLBINDBUFFERARBPROC glBindBuffer; + PFNGLBUFFERDATAARBPROC glBufferData; +#endif + +namespace Core { + int width, height; + float deltaTime; + mat4 mView, mProj, mViewProj, mModel; +} + +#include "texture.h" +#include "shader.h" +#include "mesh.h" + +enum CullMode { cfNone, cfBack, cfFront }; +enum BlendMode { bmNone, bmAlpha, bmAdd, bmMultiply, bmScreen }; + +namespace Core { + + void init() { + GetProcOGL(glActiveTexture); + GetProcOGL(glCompressedTexImage2D); + + GetProcOGL(glCreateProgram); + GetProcOGL(glDeleteProgram); + GetProcOGL(glLinkProgram); + GetProcOGL(glUseProgram); + GetProcOGL(glGetProgramInfoLog); + GetProcOGL(glCreateShader); + GetProcOGL(glDeleteShader); + GetProcOGL(glShaderSource); + GetProcOGL(glAttachShader); + GetProcOGL(glCompileShader); + GetProcOGL(glGetShaderInfoLog); + GetProcOGL(glGetUniformLocation); + GetProcOGL(glUniform1iv); + GetProcOGL(glUniform3fv); + GetProcOGL(glUniform4fv); + GetProcOGL(glUniformMatrix4fv); + GetProcOGL(glBindAttribLocation); + GetProcOGL(glEnableVertexAttribArray); + GetProcOGL(glDisableVertexAttribArray); + GetProcOGL(glVertexAttribPointer); + + GetProcOGL(glGenBuffers); + GetProcOGL(glDeleteBuffers); + GetProcOGL(glBindBuffer); + GetProcOGL(glBufferData); + } + + void free() { + // + } + + void clear(const vec4 &color) { + glClearColor(color.x, color.y, color.z, color.w); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + + void setViewport(int x, int y, int width, int height) { + glViewport(x, y, width, height); + } + + void setCulling(CullMode mode) { + switch (mode) { + case cfNone : + glDisable(GL_CULL_FACE); + case cfBack : + glCullFace(GL_BACK); + break; + case cfFront : + glCullFace(GL_FRONT); + break; + } + + if (mode != bmNone) + glEnable(GL_CULL_FACE); + } + + void setBlending(BlendMode mode) { + switch (mode) { + case bmNone : + glDisable(GL_BLEND); + break; + case bmAlpha : + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case bmAdd : + glBlendFunc(GL_ONE, GL_ONE); + break; + case bmMultiply : + glBlendFunc(GL_DST_COLOR, GL_ZERO); + break; + case bmScreen : + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR); + break; + } + + if (mode != bmNone) + glEnable(GL_BLEND); + } +} + +#endif \ No newline at end of file diff --git a/src/game.h b/src/game.h new file mode 100644 index 0000000..550190a --- /dev/null +++ b/src/game.h @@ -0,0 +1,1639 @@ +#ifndef H_GAME_TR +#define H_GAME_TR + +#include "core.h" + + #define ENTITY_LARA 0 + + #define ENTITY_ENEMY_TWIN 6 + #define ENTITY_ENEMY_WOLF 7 + #define ENTITY_ENEMY_BEAR 8 + #define ENTITY_ENEMY_BAT 9 + #define ENTITY_ENEMY_CROCODILE_LAND 10 + #define ENTITY_ENEMY_CROCODILE_WATER 11 + #define ENTITY_ENEMY_LION_MALE 12 + #define ENTITY_ENEMY_LION_FEMALE 13 + #define ENTITY_ENEMY_PUMA 14 + #define ENTITY_ENEMY_GORILLA 15 + #define ENTITY_ENEMY_RAT_LAND 16 + #define ENTITY_ENEMY_RAT_WATER 17 + #define ENTITY_ENEMY_REX 18 + #define ENTITY_ENEMY_RAPTOR 19 + #define ENTITY_ENEMY_MUTANT 20 + + #define ENTITY_ENEMY_CENTAUR 23 + #define ENTITY_ENEMY_MUMMY 24 + #define ENTITY_ENEMY_LARSON 27 + + #define ENTITY_CRYSTAL 83 + + #define ENTITY_MEDIKIT_SMALL 93 + #define ENTITY_MEDIKIT_BIG 94 + + #define ENTITY_VIEW_TARGET 169 + + #define ENTITY_TRAP_FLOOR 35 + #define ENTITY_TRAP_SPIKES 37 + #define ENTITY_TRAP_STONE 38 + #define ENTITY_TRAP_DART 40 + + #define ENTITY_SWITCH 55 + + #define ENTITY_GUN_SHOTGUN 85 + + #define ENTITY_AMMO_UZI 91 + #define ENTITY_AMMO_SHOTGUN 89 + #define ENTITY_AMMO_MAGNUM 90 + + enum LaraState { + STATE_WALK, + STATE_RUN, + STATE_STOP, + STATE_FORWARD_JUMP, + STATE_FAST_TURN, + STATE_FAST_BACK, + STATE_TURN_RIGHT, + STATE_TURN_LEFT, + STATE_DEATH, + STATE_FAST_FALL, + STATE_HANG, + STATE_REACH, + STATE_SPLAT, + STATE_TREAD, + STATE_FAST_TURN_14, + STATE_COMPRESS, + STATE_BACK, + STATE_SWIM, + STATE_GLIDE, + STATE_NULL_19, + STATE_FAST_TURN_20, + STATE_STEP_RIGHT, + STATE_STEP_LEFT, + STATE_ROLL_23, + STATE_SLIDE, + STATE_BACK_JUMP, + STATE_RIGHT_JUMP, + STATE_LEFT_JUMP, + STATE_UP_JUMP, + STATE_FALL_BACK, + STATE_HANG_LEFT, + STATE_HANG_RIGHT, + STATE_SLIDE_BACK, + STATE_SURF_TREAD, + STATE_SURF_SWIM, + STATE_DIVE, + STATE_PUSH_BLOCK, + STATE_PULL_BLOCK, + STATE_PUSH_PULL_READY, + STATE_PICK_UP, + STATE_SWITCH_ON, + STATE_SWITCH_OFF, + STATE_USE_KEY, + STATE_USE_PUZZLE, + STATE_UNDERWATER_DEATH, + STATE_ROLL_45, + STATE_SPECIAL, + STATE_SURF_BACK, + STATE_SURF_LEFT, + STATE_SURF_RIGHT, + STATE_NULL_50, + STATE_NULL_51, + STATE_SWAN_DIVE, + STATE_FAST_DIVE, + STATE_NULL_54, + STATE_WATER_OUT, + STATE_CLIMB_START_AND_STANDING, + STATE_CLIMB_UP, + STATE_CLIMB_LEFT, + STATE_CLIMB_END, + STATE_CLIMB_RIGHT, + STATE_CLIMB_DOWN, + STATE_NULL_62, + STATE_NULL_63, + STATE_NULL_64, + STATE_WADE, + STATE_WATER_ROLL, + STATE_PICK_UP_FLARE, + STATE_NULL_68, + STATE_NULL_69, + STATE_DEATH_SLIDE, + STATE_DUCK, + STATE_DUCK_72, + STATE_DASH, + STATE_DASH_DIVE }; + +float time; +vec3 laraPos(0.0f); + +typedef char int8; +typedef short int16; +typedef int int32; +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; + +#pragma pack(push, 1) + +struct tr_colour { + uint8 r, g, b; +}; + +struct tr_colour4 { + uint8 r, g, b, a; +}; + +struct tr_vertex { + int16 x, y, z; +}; + +struct tr_face4 { + uint16 vertices[4]; + uint16 texture; // 15 bit - double-sided +}; + +struct tr_face3 { + uint16 vertices[3]; + uint16 texture; +}; + +struct tr_textile8 { + uint8 index[256 * 256]; +}; + +struct tr_room_info { + int32 x, z; + int32 yBottom, yTop; +}; + +struct tr_room_portal { + uint16 roomIndex; + tr_vertex normal; + tr_vertex vertices[4]; +}; + +struct tr_room_sector { + uint16 floorIndex; // Index into FloorData[] + uint16 boxIndex; // Index into Boxes[] (-1 if none) + uint8 roomBelow; // 255 is none + int8 floor; // Absolute height of floor * 256 + uint8 roomAbove; // 255 if none + int8 ceiling; // Absolute height of ceiling * 256 +}; + +struct tr_room_light { + int32 x, y, z; // Position of light, in world coordinates + uint16 Intensity1; // Light intensity + uint32 fade; // Falloff value +}; + +struct tr_room_vertex { + tr_vertex vertex; + int16 lighting; // 0 (bright) .. 0x1FFF (dark) +}; + +struct tr_room_sprite { + int16 vertex; + int16 texture; +}; + +struct tr_room_staticmesh { + int32 x, y, z; + uint16 rotation; // (rotation >> 14) * 90 + uint16 intensity; // 0 (bright) .. 0x1FFF (dark) + uint16 meshID; +}; + +enum tr_romm_flag { FLAG_WATER = 1 }; + +struct tr_room_data { + uint32 size; // Number of data words (uint16_t's) + int align; + Vector vertices; + Vector rectangles; + Vector triangles; + Vector sprites; + + tr_room_data(Stream *stream) : + size(stream->read(size)), + align(stream->pos), + vertices(stream), + rectangles(stream), + triangles(stream), + sprites(stream) { stream->seek((align + size * 2) - stream->pos); } +}; + +struct tr_room { + tr_room_info info; // Where the room exists, in world coordinates + tr_room_data data; // The room mesh + + Vector portals; // List of visibility portals + + uint16 zSectors; // ``Width'' of sector list + uint16 xSectors; // ``Height'' of sector list + Vector sectors; // List of sectors in this room + + int16 ambient; // 0 (bright) .. 0x1FFF (dark) + + Vector lights; // List of lights + + Vector meshes; // List of static meshes + + int16 alternateRoom; + int16 flags; // RoomFlag + + bool visible; + + tr_room(Stream *stream) : + info(stream->read(info)), + data(stream), + portals(stream), + zSectors(stream->read(zSectors)), + xSectors(stream->read(xSectors)), + sectors(stream, (int)zSectors * (int)xSectors), + ambient(stream->read(ambient)), + lights(stream), + meshes(stream), + alternateRoom(stream->read(alternateRoom)), + flags(stream->read(flags)) {}; +}; + +struct tr_mesh { + tr_vertex center; + int32 radius; + + Vector vertices; // List of vertices (relative coordinates) + + int16 nCount; + Vector normals; + Vector lights; // nCount < 0 (abs(nCount)) + + Vector rectangles; // list of textured rectangles + Vector triangles; // list of textured triangles + Vector crectangles; // list of coloured rectangles + Vector ctriangles; // list of coloured triangles + + tr_mesh(Stream *stream) : + center(stream->read(center)), + radius(stream->read(radius)), + vertices(stream), + nCount(stream->read(nCount)), + normals(stream, nCount < 0 ? 0 : nCount), + lights(stream, nCount > 0 ? 0 : abs(nCount)), + rectangles(stream), + triangles(stream), + crectangles(stream), + ctriangles(stream) {} +}; + +struct tr_staticmesh { + uint32 ID; // Static Mesh Identifier + uint16 mesh; // Mesh (offset into MeshPointers[]) + tr_vertex vBox[2]; + tr_vertex cBox[2]; + uint16 Flags; +}; + +struct tr_entity { + int16 id; // Object Identifier (matched in Models[], or SpriteSequences[], as appropriate) + int16 room; // which room contains this item + int32 x, y, z; // world coords + int16 rotation; // ((0xc000 >> 14) * 90) degrees + int16 intensity; // (constant lighting; -1 means use mesh lighting) + uint16 flags; // 0x0100 indicates "initially invisible", 0x3e00 is Activation Mask + // 0x3e00 indicates "open" or "activated"; these can be XORed with + // related FloorData::FDlist fields (e.g. for switches) +}; + +struct tr_sprite_texture { + uint16 tile; + uint8 u, v; + uint16 w, h; // (ActualValue * 256) + 255 + int16 l, t, r, b; +}; + +struct tr_sprite_sequence { + int32 id; // Sprite identifier + int16 sCount; // Negative of ``how many sprites are in this sequence'' + int16 sStart; // Where (in sprite texture list) this sequence starts +}; + +struct tr_meshtree { + uint32 flags; + int32 x, y, z; +}; + +struct fixed { + uint16 L; + int16 H; + float toFloat() { + return H + L / 65535.0f; + } +}; + +struct tr_animation { + uint32 frameOffset; // Byte offset into Frames[] (divide by 2 for Frames[i]) + uint8 frameRate; // Engine ticks per frame + uint8 frameSize; // Number of int16_t's in Frames[] used by this animation + + uint16 state; + + fixed speed; + fixed accel; + + uint16 frameStart; // First frame in this animation + uint16 frameEnd; // Last frame in this animation + uint16 nextAnimation; + uint16 nextFrame; + + uint16 scCount; + uint16 scOffset; // Offset into StateChanges[] + + uint16 acCount; // How many of them to use. + uint16 animCommand; // Offset into AnimCommand[] +}; + +struct tr_state_change { + uint16 state; + uint16 rCount; // number of ranges + uint16 rangeOffset; // Offset into animRanges[] +}; + +struct tr_anim_range { + int16 low; // Lowest frame that uses this range + int16 high; // Highest frame that uses this range + int16 nextAnimation; // Animation to dispatch to + int16 nextFrame; // Frame offset to dispatch to +}; + +struct tr_anim_frame { + int16 minX, minY, minZ; // Bounding box (low) + int16 maxX, maxY, maxZ; // Bounding box (high) + int16 x, y, z; // Starting offset for this model + int16 aCount; + uint16 angles[0]; // angle frames in YXZ order +}; + +struct tr_anim_texture { + int16 tCount; // Actually, this is the number of texture ID's - 1 + int16 textures[0]; // offsets into ObjectTextures[], in animation order +}; + +struct tr_box { + int32 minZ, maxZ; // Horizontal dimensions in global units + int32 minX, maxX; + int16 floor; // Height value in global units + int16 overlap; // Index into Overlaps[]. +}; + +struct tr_zone // 12 bytes +{ + uint16 GroundZone1_Normal; + uint16 GroundZone2_Normal; + uint16 FlyZone_Normal; + uint16 GroundZone1_Alternate; + uint16 GroundZone2_Alternate; + uint16 FlyZone_Alternate; +}; + +struct tr_sample_source { + int32 x, y, z; // absolute position of sound source (world coordinates) + uint16 id; // internal sample index + uint16 flags; // 0x40, 0x80, or 0xC0 +}; + +struct tr_sample_info { + uint16 sample; // (index into SampleIndices) -- NOT USED IN TR4-5!!! + uint16 volume; + uint16 chance; // If !=0 and ((rand()&0x7fff) > Chance), this sound is not played + uint16 flags; // Bits 0-1: Looped flag, bits 2-5: num samples, bits 6-7: UNUSED +}; + +struct tr_object_texture_vert { + uint8 Xcoordinate; // 1 if Xpixel is the low value, 255 if Xpixel is the high value in the object texture + uint8 Xpixel; + uint8 Ycoordinate; // 1 if Ypixel is the low value, 255 if Ypixel is the high value in the object texture + uint8 Ypixel; +}; + +struct tr_object_texture { + uint16 attribute; // 0 - opaque, 1 - transparent, 2 - blend additive + uint16 tileAndFlag; // 0..14 - tile, 15 - is triangle + tr_object_texture_vert vertices[4]; // The four corners of the texture +}; + +struct tr_camera { + int32 x, y, z; + int16 room; + uint16 flags; +}; + +struct tr_cinematic_frame +{ + int16 rotY; // rotation about Y axis, +/- 32767 == +/- 180 degrees + int16 rotZ; // rotation about Z axis, +/- 32767 == +/- 180 degrees + int16 rotZ2; // seems to work a lot like rotZ; I haven't yet been able to + // differentiate them + int16 posZ; // camera position relative to something (target? Lara? room + // origin?). pos* are _not_ in world coordinates. + int16 posY; // camera position relative to something (see posZ) + int16 posX; // camera position relative to something (see posZ) + int16 unknown; // changing this can cause a runtime error + int16 rotX; // rotation about X axis, +/- 32767 == +/- 180 degrees +}; + +struct tr_model { + uint32 id; // Item Identifier (matched in Entities[]) + uint16 mCount; // number of meshes in this object + uint16 mStart; // stating mesh (offset into MeshPointers[]) + uint32 mTree; // offset into MeshTree[] + uint32 frame; // byte offset into Frames[] (divide by 2 for Frames[i]) + uint16 animation; // offset into Animations[] +}; + +#include + +struct tr_level { + Texture **textures; + + uint32 version; // version (4 bytes) + + Vector tiles; // 8-bit (palettized) textiles (NumTextiles * 65536 bytes) + + uint32 unused; // 32-bit unused value (4 bytes) + + Array rooms; + Vector floors; // floor data (NumFloorData * 2 bytes) + int meshesData; + Vector meshBuffer; + Array meshes; // note that NumMeshPointers comes AFTER Meshes[] + Vector meshPointers; // mesh pointer list (NumMeshPointers * 4 bytes) + Vector animations; // animation list (NumAnimations * 32 bytes) + Vector stateChanges; // state-change list (NumStructures * 6 bytes) + Vector animRanges; // animation-dispatch list list (NumAnimDispatches * 8 bytes) + Vector animCommands; // animation-command list (NumAnimCommands * 2 bytes) + Vector meshTrees; // MeshTree list (NumMeshTrees * 4*4 bytes) + Vector frames; // frame data (NumFrames * 2 bytes) + Vector models; // model list (NumModels * 18 bytes) + Vector staticMeshes; // StaticMesh data (NumStaticMesh * 32 bytes) + Vector objectTextures; // object texture list (NumObjectTextures * 20 bytes) (after AnimatedTextures in TR3) + Vector spriteTextures; // sprite texture list (NumSpriteTextures * 16 bytes) + Vector spriteSequences; // sprite sequence data (NumSpriteSequences * 8 bytes) + Vector cameras; // camera data (NumCameras * 16 bytes) + Vector sampleSources; // sound source data (NumSoundSources * 16 bytes) + Vector boxes; // box data (NumBoxes * 20 bytes [TR1 version]) + Vector overlaps; // overlap data (NumOverlaps * 2 bytes) + + Vector groundZone1; // ground zone data [2*NumBoxes] + Vector groundZone2; // ground zone 2 data [2*NumBoxes] + Vector flyZone; // fly zone data [2*NumBoxes] + + Vector animatedTextures; // animated texture data (NumAnimatedTextures * 2 bytes) + Vector entities; // item list (NumEntities * 22 bytes [TR1 version]) + Vector lightMap; // light map (8192 bytes) + Vector palette; // 8-bit palette (768 bytes) + Vector cinematicFrames; // (NumCinematicFrames * 16 bytes) + Vector demoData; // demo data (NumDemoData bytes) + Vector samplesMap; // sound map (512 bytes) + Vector samplesInfo; // sound-detail list (NumSoundDetails * 8 bytes) + Vector samples; // (array of uint8_t's -- embedded sound samples in Microsoft WAVE format) + Vector sampleIndices; // sample indices (NumSampleIndices * 4 bytes) + + struct Controller { + tr_level *level; + tr_animation *anim; + float fTime; + + vec3 pos; + float angle; + + int state; // LaraState + int lastFrame; + + Controller(tr_level *level) : level(level), pos(0.0f), angle(0.0f), fTime(0.0f) { + anim = &level->animations[0]; + lastFrame = 0; + } + + void update() { + float rot = 0.0f; + state = STATE_STOP; + if (Input::down[ikShift]) { + if (Input::down[ikUp]) { state = STATE_WALK; }; + if (Input::down[ikDown]) { state = STATE_BACK; }; + if (Input::down[ikLeft]) { if (!Input::down[ikUp] && !Input::down[ikDown]) { state = STATE_STEP_LEFT; } else rot = -Core::deltaTime * PI; }; + if (Input::down[ikRight]) { if (!Input::down[ikUp] && !Input::down[ikDown]) { state = STATE_STEP_RIGHT; } else rot = Core::deltaTime * PI; }; + } else + if (Input::down[ikSpace]) { + if (anim->state == STATE_RUN) + state = STATE_FORWARD_JUMP; + else + if (Input::down[ikUp]) + state = anim->state != STATE_COMPRESS ? STATE_COMPRESS : STATE_FORWARD_JUMP; + else + if (Input::down[ikDown]) + state = anim->state != STATE_COMPRESS ? STATE_COMPRESS : STATE_BACK_JUMP; + else + if (Input::down[ikLeft]) + state = anim->state != STATE_COMPRESS ? STATE_COMPRESS : STATE_LEFT_JUMP; + else + if (Input::down[ikRight]) + state = anim->state != STATE_COMPRESS ? STATE_COMPRESS : STATE_RIGHT_JUMP; + else + state = STATE_UP_JUMP; + } else { + if (Input::down[ikUp]) { state = STATE_RUN; }; + if (Input::down[ikDown]) { state = STATE_FAST_BACK; }; + if (Input::down[ikLeft]) { if (!Input::down[ikUp] && !Input::down[ikDown]) state = STATE_TURN_LEFT; rot = -Core::deltaTime * PI; }; + if (Input::down[ikRight]) { if (!Input::down[ikUp] && !Input::down[ikDown]) state = STATE_TURN_RIGHT; rot = Core::deltaTime * PI; }; + } + + if (Input::down[ikEnter]) + state = STATE_COMPRESS; + + + fTime += Core::deltaTime; + int fCount = anim->frameEnd - anim->frameStart + 1; + int fIndex = int(fTime * 30.0f); + + // LOG("%d / %d\n", fIndex, fCount); + + // fIndex = anim->frameStart + (fIndex % fCount); + //LOG("%d\n", fIndex); + + /* + if (anim->state == state) { + for (int i = 0; i < anim->scCount; i++) { + auto &sc = level->stateChanges[anim->scOffset + i]; + LOG("%d ", sc.state); + } + LOG("\n"); + } + */ + if (anim->state != state) { + for (int i = 0; i < anim->scCount; i++) { + auto &sc = level->stateChanges[anim->scOffset + i]; + if (sc.state == state) { + for (int j = 0; j < sc.rCount; j++) { + auto &range = level->animRanges[sc.rangeOffset + j]; + if ( anim->frameStart + fIndex >= range.low && anim->frameStart + fIndex <= range.high) { + int st = anim->state; + anim = &level->animations[range.nextAnimation]; + fTime = 0.0f;//(ad.nextFrame - anim->frameStart) / (30.0f / anim->frameRate); + fIndex = range.nextFrame - anim->frameStart; + fCount = anim->frameEnd - anim->frameStart + 1; + // LOG("set anim %d %f %f %d -> %d -> %d\n", range.nextAnimation, anim->accel.toFloat(), anim->speed.toFloat(), st, state, anim->state); + + //LOG("from %f to %f\n", s, s + a * c); + // LOG("frame: %d\n", fIndex); + break; + } + } + break; + } + } + }; + + if (fIndex >= fCount) { + fIndex = anim->nextFrame; + int id = anim->nextAnimation; + anim = &level->animations[anim->nextAnimation]; +// LOG("nxt anim %d %f %f %d %d\n", id, anim->accel.toFloat(), anim->speed.toFloat(), anim->frameRate, anim->frameEnd - anim->frameStart + 1); + + // LOG("from %f to %f\n", s, s + a * c / 30.0f); + + fIndex -= anim->frameStart; +// LOG("frame: %d\n", fIndex); + fTime = (fIndex) / 30.0f; + //fCount = anim->frameEnd - anim->frameStart + 1; + //LOG("reset\n"); + } + + if (anim->state == state) { + angle += rot; + } + + int16 *ptr = &level->animCommands[anim->animCommand]; + + for (int i = 0; i < anim->acCount; i++) { + switch (*ptr++) { + case 0x01 : { // cmd position + int16 sx = *ptr++; + int16 sy = *ptr++; + int16 sz = *ptr++; + LOG("move: %d %d\n", (int)sx, (int)sy, (int)sz); + break; + } + case 0x02 : { // cmd jump speed + int16 sx = *ptr++; + int16 sz = *ptr++; + LOG("jump: %d %d\n", (int)sx, (int)sz); + break; + } + case 0x03 : // empty hands + break; + case 0x04 : // kill + break; + case 0x05 : { // play sound + int frame = (*ptr++); + int id = (*ptr++) & 0x3FFF; + if (fIndex == frame - anim->frameStart && fIndex != lastFrame) { + auto a = level->samplesMap[id]; + auto b = level->samplesInfo[a].sample; + auto c = level->sampleIndices[b]; + + void *p = &level->samples[c]; + + PlaySound((LPSTR)p, NULL, SND_ASYNC | SND_MEMORY); + } + break; + } + case 0x06 : + ptr += 2; + break; + } + } + + + float d = 0.0f; + + switch (anim->state) { + case STATE_BACK : + case STATE_BACK_JUMP : + case STATE_FAST_BACK : + d = PI; + break; + case STATE_STEP_LEFT : + case STATE_LEFT_JUMP : + d = -PI * 0.5f; + break; + case STATE_STEP_RIGHT : + case STATE_RIGHT_JUMP : + d = PI * 0.5f; + break; + } + d += angle; + + float speed = anim->speed.toFloat() + anim->accel.toFloat() * (fTime * 30.0f); + pos = pos + vec3(sinf(d), 0, cosf(d)) * (speed * Core::deltaTime * 30.0f); + + + lastFrame = fIndex; + } + + } *lara; + + tr_level(Stream *stream) : + version(stream->read(version)), + tiles(stream), + unused(stream->read(unused)), + rooms(stream), + floors(stream), + meshesData(stream->pos + 4), + meshBuffer(stream), + meshPointers(stream), + animations(stream), + stateChanges(stream), + animRanges(stream), + animCommands(stream), + meshTrees(stream), + frames(stream), + models(stream), + staticMeshes(stream), + objectTextures(stream), + spriteTextures(stream), + spriteSequences(stream), + cameras(stream), + sampleSources(stream), + boxes(stream), + overlaps(stream), + groundZone1(stream, boxes.count * 2), + groundZone2(stream, boxes.count * 2), + flyZone(stream, boxes.count * 2), + animatedTextures(stream), + entities(stream), + lightMap(stream, 32 * 256), + palette(stream, 256), + cinematicFrames(stream), + demoData(stream), + samplesMap(stream, 256), + samplesInfo(stream), + samples(stream), + sampleIndices(stream) { + + int pos = stream->pos; + + meshes = Array(); + meshes.count = meshPointers.count; + meshes.items = new tr_mesh*[meshes.count]; + + for (int i = 0; i < meshPointers.count; i++) { + stream->setPos(meshesData + meshPointers[i]); + meshes.items[i] = new tr_mesh(stream); + } + + // God bless Amiga + int m = 0; + for (int i = 0; i < palette.count; i++) { + tr_colour &c = palette[i]; + c.r <<= 2; + c.g <<= 2; + c.b <<= 2; + } + + if (tiles.count) { + textures = new Texture*[tiles.count]; + + for (int i = 0; i < tiles.count; i++) { + // sprintf(buf, "LEVEL1_%d.PVR", i); + // textures[i] = Core::load(buf); + textures[i] = getTexture(i); + } + } else + textures = NULL; + + lara = new Controller(this); + } + + ~tr_level() { + delete lara; + + for (int i = 0; i < tiles.count; i++) + delete textures[i]; + delete[] textures; + } + + Texture *getTexture(int tile) { + tr_colour4 data[256 * 256]; + for (int i = 0; i < 256 * 256; i++) { + int index = tiles[tile].index[i]; + auto p = palette[index]; + data[i].r = p.r; + data[i].g = p.g; + data[i].b = p.b; + data[i].a = index == 0 ? 0 : 255; + } + return new Texture(256, 256, 0, data); + } + + #define SCALE (1.0f / 1024.0f / 2.0f) + + void renderRoom(tr_room *room) { + glPushMatrix(); + glScalef(-SCALE, -SCALE, SCALE); + glTranslatef(room->info.x, 0.0f, room->info.z); + + // rectangles + for (int j = 0; j < room->data.rectangles.count; j++) { + auto &f = room->data.rectangles[j]; + auto &t = objectTextures[f.texture]; + setTexture(f.texture); + + glBegin(GL_QUADS); + for (int k = 0; k < 4; k++) { + auto &v = room->data.vertices[f.vertices[k]]; + float a = 1.0f - v.lighting / 8191.0f; + glColor3f(a, a, a); + glTexCoord2f(t.vertices[k].Xpixel / 256.0f, t.vertices[k].Ypixel / 256.0f); + glVertex3f(v.vertex.x, v.vertex.y, v.vertex.z); + } + glEnd(); + } + + // triangles + for (int j = 0; j < room->data.triangles.count; j++) { + auto &f = room->data.triangles[j]; + auto &t = objectTextures[f.texture]; + setTexture(f.texture); + + glBegin(GL_TRIANGLES); + for (int k = 0; k < 3; k++) { + auto &v = room->data.vertices[f.vertices[k]]; + float a = 1.0f - v.lighting / 8191.0f; + glColor3f(a, a, a); + glTexCoord2f(t.vertices[k].Xpixel / 256.0f, t.vertices[k].Ypixel / 256.0f); + glVertex3f(v.vertex.x, v.vertex.y, v.vertex.z); + } + glEnd(); + } + glPopMatrix(); + + // meshes + float a = 1.0f - room->ambient / 8191.0f; + + for (int j = 0; j < room-> meshes.count; j++) { + auto rMesh = room->meshes[j]; + auto sMesh = getMeshByID(rMesh.meshID); + ASSERT(sMesh != NULL); + + glPushMatrix(); + glScalef(-SCALE, -SCALE, SCALE); + glTranslatef(rMesh.x, rMesh.y, rMesh.z); + glRotatef((rMesh.rotation >> 14) * 90.0f, 0, 1, 0); + + renderMesh(meshes[sMesh->mesh], vec3(a)); + + glPopMatrix(); + } + + // sprites + Core::setBlending(bmAlpha); + for (int j = 0; j < room->data.sprites.count; j++) + renderSprite(room, &room->data.sprites[j]); + } + + void renderMesh(tr_mesh *mesh, const vec3 &color) { + if (mesh->nCount > 0) + glEnable(GL_LIGHTING); + glColor3f(color.x, color.y, color.z); + + // triangles + for (int j = 0; j < mesh->triangles.count; j++) { + auto &f = mesh->triangles[j]; + auto &t = objectTextures[f.texture]; + setTexture(f.texture); + + glBegin(GL_TRIANGLES); + for (int k = 0; k < 3; k++) { + auto &v = mesh->vertices[f.vertices[k]]; + + if (mesh->nCount > 0) { + auto vn = mesh->normals[f.vertices[k]]; + vec3 n = vec3(vn.x, vn.y, vn.z).normal(); + glNormal3f(n.x, n.y, n.z); + } else { + auto l = mesh->lights[f.vertices[k]]; + float a = 1.0f - l / 8191.0f; + glColor3f(a, a, a); + } + + glTexCoord2f(t.vertices[k].Xpixel / 256.0f, t.vertices[k].Ypixel / 256.0f); + glVertex3f(v.x, v.y, v.z); + } + glEnd(); + } + + // rectangles + for (int j = 0; j < mesh->rectangles.count; j++) { + auto &f = mesh->rectangles[j]; + auto &t = objectTextures[f.texture]; + setTexture(f.texture); + + glBegin(GL_QUADS); + for (int k = 0; k < 4; k++) { + auto &v = mesh->vertices[f.vertices[k]]; + + if (mesh->nCount > 0) { + auto vn = mesh->normals[f.vertices[k]]; + vec3 n = vec3(vn.x, vn.y, vn.z).normal(); + glNormal3f(n.x, n.y, n.z); + } else { + auto l = mesh->lights[f.vertices[k]]; + float a = 1.0f - l / 8191.0f; + glColor3f(a, a, a); + } + glTexCoord2f(t.vertices[k].Xpixel / 256.0f, t.vertices[k].Ypixel / 256.0f); + glVertex3f(v.x, v.y, v.z); + } + glEnd(); + } + + glDisable(GL_TEXTURE_2D); + // debug normals + /* + if (mesh->nCount > 0) { + glDisable(GL_LIGHTING); + glColor3f(0, 0, 1); + glBegin(GL_LINES); + for (int j = 0; j < mesh->triangles.count; j++) { + auto &f = mesh->triangles[j]; + for (int k = 0; k < 3; k++) { + auto &v = mesh->vertices[f.vertices[k]]; + auto vn = mesh->normals[f.vertices[k]]; + vec3 n = vec3(vn.x, vn.y, vn.z).normal() * 100.0f; + glVertex3f(v.x, v.y, v.z); + glVertex3f(v.x + n.x, v.y + n.y, v.z + n.z); + + } + } + glEnd(); + glEnable(GL_LIGHTING); + } + */ + + // triangles (colored) + for (int j = 0; j < mesh->ctriangles.count; j++) { + auto &f = mesh->ctriangles[j]; + auto &c = palette[f.texture & 0xFF]; + + glBegin(GL_TRIANGLES); + for (int k = 0; k < 3; k++) { + auto &v = mesh->vertices[f.vertices[k]]; + + if (mesh->nCount > 0) { + auto vn = mesh->normals[f.vertices[k]]; + vec3 n = vec3(vn.x, vn.y, vn.z).normal(); + glColor3f(c.r / 255.0f * color.x, c.g / 255.0f * color.y, c.b / 255.0f * color.z); + glNormal3f(n.x, n.y, n.z); + } else { + auto l = mesh->lights[f.vertices[k]]; + float a = (1.0f - l / 8191.0f) / 255.0f; + glColor3f(c.r * a, c.g * a, c.b * a); + } + glVertex3f(v.x, v.y, v.z); + } + glEnd(); + } + // rectangles (colored) + for (int j = 0; j < mesh->crectangles.count; j++) { + auto &f = mesh->crectangles[j]; + auto &c = palette[f.texture & 0xFF]; + + glBegin(GL_QUADS); + for (int k = 0; k < 4; k++) { + auto &v = mesh->vertices[f.vertices[k]]; + + if (mesh->nCount > 0) { + auto vn = mesh->normals[f.vertices[k]]; + vec3 n = vec3(vn.x, vn.y, vn.z).normal(); + glColor3f(c.r / 255.0f * color.x, c.g / 255.0f * color.y, c.b / 255.0f * color.z); + glNormal3f(n.x, n.y, n.z); + } else { + auto l = mesh->lights[f.vertices[k]]; + float a = (1.0f - l / 8191.0f) / 255.0f; + glColor3f(c.r * a, c.g * a, c.b * a); + } + glVertex3f(v.x, v.y, v.z); + } + glEnd(); + } + glEnable(GL_TEXTURE_2D); + + if (mesh->nCount > 0) + glDisable(GL_LIGHTING); + } + + void renderSprite(tr_sprite_texture *sprite) { + float u0 = sprite->u / 256.0f; + float v0 = sprite->v / 255.0f; + float u1 = u0 + sprite->w / (256.0f * 256.0f); + float v1 = v0 + sprite->h / (256.0f * 256.0f); + + mat4 m = Core::mView.inverse(); + vec3 up = m.up.xyz * vec3(-1, -1, 1) * (-1); + vec3 right = m.right.xyz * vec3(-1, -1, 1); + + vec3 p[4]; + p[0] = right * sprite->r + up * sprite->b; + p[1] = right * sprite->l + up * sprite->b; + p[2] = right * sprite->l + up * sprite->t; + p[3] = right * sprite->r + up * sprite->t; + + textures[sprite->tile]->bind(0); + glBegin(GL_QUADS); + glTexCoord2f(u0, v1); + glVertex3fv((GLfloat*)&p[0]); + glTexCoord2f(u1, v1); + glVertex3fv((GLfloat*)&p[1]); + glTexCoord2f(u1, v0); + glVertex3fv((GLfloat*)&p[2]); + glTexCoord2f(u0, v0); + glVertex3fv((GLfloat*)&p[3]); + glEnd(); + } + + void renderSprite(tr_room *room, tr_room_sprite *sprite) { + auto &v = room->data.vertices[sprite->vertex]; + float a = 1.0f - v.lighting / (float)0x1FFF; + glColor3f(a, a, a); + + glPushMatrix(); + glScalef(-SCALE, -SCALE, SCALE); + glTranslatef(v.vertex.x + room->info.x, v.vertex.y, v.vertex.z + room->info.z); + + renderSprite(&spriteTextures[sprite->texture]); + + glPopMatrix(); + } + + vec3 getAngle(tr_anim_frame *frame, int index) { + #define ANGLE_SCALE (2.0f * PI / 1024.0f) + + uint16 b = frame->angles[index * 2 + 0]; + uint16 a = frame->angles[index * 2 + 1]; + +// vec3 k(1.0f); + /* + if (a & 0xC000) + k = vec3::Z; + else + if (a & 0x8000) + k = vec3::Y; + else + if (a & 0x4000) + k = vec3::X; + */ +// k = k * ANGLE_SCALE; + + return vec3((a & 0x3FF0) >> 4, ( ((a & 0x000F) << 6) | ((b & 0xFC00) >> 10)), b & 0x03FF) * ANGLE_SCALE; + } + + float lerpAngle(float a, float b, float t) { + float d = b - a; + if (d >= PI) + a += PI * 2.0f; + else + if (d <= -PI) + a -= PI * 2.0f; + return a + (b - a) * t; + } + + quat lerpAngle(const vec3 &a, const vec3 &b, float t) { + /* + return vec3(lerpAngle(a.x, b.x, t), + lerpAngle(a.y, b.y, t), + lerpAngle(a.z, b.z, t)); + */ + mat4 ma, mb; + ma.identity(); + mb.identity(); + + ma.rotateY(a.y); + ma.rotateX(a.x); + ma.rotateZ(a.z); + + mb.rotateY(b.y); + mb.rotateX(b.x); + mb.rotateZ(b.z); + + return ma.getRot().slerp(mb.getRot(), t).normal(); + } + + float debugTime = 0.0f; + + void renderModel(tr_model *model) { + mat4 m; + m.identity(); + + tr_animation *anim = &animations[model->animation]; + + float fTime = time; + + if (model->id == ENTITY_LARA) { + anim = lara->anim; + fTime = lara->fTime; + m.translate(lara->pos); + m.rotateY(lara->angle); + } + + float k = fTime * 30.0f / anim->frameRate; + int fIndex = (int)k; + int fCount = (anim->frameEnd - anim->frameStart) / anim->frameRate + 1; + + int fSize = sizeof(tr_anim_frame) + model->mCount * sizeof(uint16) * 2; + k = k - fIndex; + + + int fIndexA = fIndex % fCount, fIndexB = (fIndex + 1) % fCount; + tr_anim_frame *frameA = (tr_anim_frame*)&frames[(anim->frameOffset + fIndexA * fSize) >> 1]; + + tr_animation *nextAnim = NULL; + + if (fIndexB == 0) { + nextAnim = &animations[anim->nextAnimation]; + fIndexB = (anim->nextFrame - nextAnim->frameStart) / anim->frameRate; + } else + nextAnim = anim; + +// LOG("%d %f\n", fIndexA, fTime); + + + tr_anim_frame *frameB = (tr_anim_frame*)&frames[(nextAnim->frameOffset + fIndexB * fSize) >> 1]; +/* + anim = &animations[0]; + + if (Input::down[ikO]) + debugTime += 2.0f * Core::deltaTime; + fCount = (anim->frameEnd - anim->frameStart) / anim->frameRate + 1; + + fIndex = ((int)debugTime) % fCount; + + + LOG("%f %d / %d (%d, %d)\n", debugTime, fIndex, fCount, int(anim->frameEnd - anim->frameStart), int(anim->frameRate) ); + + + frameA = frameB = (tr_anim_frame*)&frames[(anim->frameOffset + fIndex * fSize) >> 1]; + k = 0.0f; +*/ + + /* + + float k = fTime * 30.0f / anim->frameRate; + + int fCount = (anim->frameEnd - anim->frameStart + 1);// / anim->frameRate; + ASSERT(fCount % anim->frameRate == 0); + fCount /= anim->frameRate; + + int fIndex = int(k); + k = k - (int)k; +*/ +// ASSERT(fpSize == fSize); +// fSize = fpSize; + + // LOG("%d\n", fIndex % fCount); + //if (fCount > 1) LOG("%d %d\n", model->id, fCount); + // LOG("%d\n", fIndex % fCount); + + +// Debug::Draw::box(Box(vec3(frameA->minX, frameA->minY, frameA->minZ), vec3(frameA->maxX, frameA->maxY, frameA->maxZ))); + + tr_meshtree *tree = (int)model->mTree < meshTrees.count ? (tr_meshtree*)&meshTrees[model->mTree] : NULL; + + int sIndex = 0; + mat4 stack[20]; + + m.translate(vec3(frameA->x, frameA->y, frameA->z).lerp(vec3(frameB->x, frameB->y, frameB->z), k)); + + for (int i = 0; i < model->mCount; i++) { + + if (i > 0 && tree) { + tr_meshtree &t = tree[i - 1]; + + if (t.flags & 0x01) m = stack[--sIndex]; + if (t.flags & 0x02) stack[sIndex++] = m; + + ASSERT(sIndex >= 0); + ASSERT(sIndex < 20); + + m.translate(vec3(t.x, t.y, t.z)); + } + + quat q = lerpAngle(getAngle(frameA, i), getAngle(frameB, i), k); + m = m * mat4(q, vec3(0.0f)); + + /* + vec3 angle = lerpAngle(getAngle(frameA, i), getAngle(frameB, i), k); + m.rotateY(angle.y); + m.rotateX(angle.x); + m.rotateZ(angle.z); + */ + + glPushMatrix(); + glMultMatrixf((GLfloat*)&m); + renderMesh(meshes[model->mStart + i], vec3(1.0f)); + glPopMatrix(); + } + } + + void renderEntity(tr_entity *entity) { + glPushMatrix(); + glScalef(-SCALE, -SCALE, SCALE); + glTranslatef(entity->x, entity->y, entity->z); + + if (entity->intensity > -1) { + float a = 1.0f - entity->intensity / (float)0x1FFF; + glColor3f(a, a, a); + } else + glColor3f(1, 1, 1); + + + for (int i = 0; i < models.count; i++) + if (entity->id == models[i].id) { + glRotatef((entity->rotation >> 14) * 90.0f, 0, 1, 0); + renderModel(&models[i]); + break; + } + + for (int i = 0; i < spriteSequences.count; i++) + if (entity->id == spriteSequences[i].id) { + renderSprite(&spriteTextures[spriteSequences[i].sStart]); + break; + } + + glPopMatrix(); + } + + /* + void debugPortals() { + glDisable(GL_TEXTURE_2D); + Core::setBlending(bmAdd); + glColor3f(0, 0.25f, 0); + glDepthMask(GL_FALSE); + + glPushMatrix(); + glScalef(-SCALE, -SCALE, SCALE); + + glBegin(GL_QUADS); + for (int i = 0; i < rooms.count; i++) { + int x = rooms[i]->info.x; + int z = rooms[i]->info.z; + for (int j = 0; j < rooms[i]->portals.count; j++) { + auto &p = rooms[i]->portals[j]; + for (int k = 0; k < 4; k++) { + auto &v = p.vertices[k]; + glVertex3f(v.x + x, v.y, v.z + z); + } + } + } + glEnd(); + + glPopMatrix(); + + glDepthMask(GL_TRUE); + glEnable(GL_TEXTURE_2D); + Core::setBlending(bmAlpha); + } + + void debugFloor(const vec3 &f, const vec3 &c, int floorIndex) { + vec3 vf[4] = { f, f + vec3(1024, 0, 0), f + vec3(1024, 0, 1024), f + vec3(0, 0, 1024) }; + vec3 vc[4] = { c, c + vec3(1024, 0, 0), c + vec3(1024, 0, 1024), c + vec3(0, 0, 1024) }; + + auto *d = &floors[floorIndex]; + auto cmd = *d; + + do { + cmd = *d; + int func = cmd & 0x001F; // function + int sub = (cmd & 0x7F00) >> 8; // sub function + d++; + + + if (func == 0x00) { // portal + // d++; + } + + if ((func == 0x02 || func == 0x03) && sub == 0x00) { // floor & ceiling corners + int sx = 256 * (int)(int8)(*d & 0x00FF); + int sz = 256 * (int)(int8)((*d & 0xFF00) >> 8); + + auto &p = func == 0x02 ? vf : vc; + + if (func == 0x02) { + + if (sx > 0) { + p[0].y += sx; + p[3].y += sx; + } else { + p[1].y -= sx; + p[2].y -= sx; + } + + if (sz > 0) { + p[0].y += sz; + p[1].y += sz; + } else { + p[3].y -= sz; + p[2].y -= sz; + } + + } else { + + if (sx < 0) { + p[0].y += sx; + p[3].y += sx; + } else { + p[1].y -= sx; + p[2].y -= sx; + } + + if (sz > 0) { + p[0].y -= sz; + p[1].y -= sz; + } else { + p[3].y += sz; + p[2].y += sz; + } + + } + + // d++; + } + + d++; + + + // LOG("%d %d\n", func, sub); + } while ((cmd & 0x8000) == 0); // end + + glColor3f(0, 1, 0); + glBegin(GL_LINE_STRIP); + for (int i = 0; i < 5; i++) + glVertex3fv((GLfloat*)&vf[i % 4]); + glEnd(); + + glColor3f(1, 0, 0); + glBegin(GL_LINE_STRIP); + for (int i = 0; i < 5; i++) + glVertex3fv((GLfloat*)&vc[i % 4]); + glEnd(); + } + + void debugSectors(tr_room *room) { + vec3 p = (Core::viewPos / SCALE - vec3(-room->info.x, 0, room->info.z)) / vec3(1024, 1, 1024); + int px = (int)-p.x; + int pz = (int)p.z; + + for (int z = 0; z < room->zSectors; z++) + for (int x = 0; x < room->xSectors; x++) { + auto &s = room->sectors[x * room->zSectors + z]; + vec3 f(x * 1024 + room->info.x, s.floor * 256, z * 1024 + room->info.z); + vec3 c(x * 1024 + room->info.x, s.ceiling * 256, z * 1024 + room->info.z); + + debugFloor(f, c, s.floorIndex); + } + } + + void debugRooms() { + glDisable(GL_TEXTURE_2D); + Core::setBlending(bmAdd); + glColor3f(0, 0.25f, 0); + glDepthMask(GL_FALSE); + + glPushMatrix(); + glScalef(-SCALE, -SCALE, SCALE); + + for (int i = 0; i < rooms.count; i++) { + auto &r = *rooms[i]; + vec3 p = vec3(r.info.x, r.info.yTop, r.info.z); + if (isInsideRoom(Core::viewPos, rooms[i])) { + debugSectors(rooms[i]); + glColor3f(0, 1, 0); + } else + glColor3f(1, 1, 1); + + Debug::Draw::box(Box(p, p + vec3(r.xSectors * 1024, r.info.yBottom - r.info.yTop, r.zSectors * 1024))); + } + + glPopMatrix(); + + glDepthMask(GL_TRUE); + glEnable(GL_TEXTURE_2D); + Core::setBlending(bmAlpha); + } + + void debugMeshes() { + glPushMatrix(); + glScalef(-SCALE, -SCALE, SCALE); + for (int i = 0; i < meshes.count; i++) { + renderMesh(meshes[i], vec3(1.0f)); + glTranslatef(-128, 0, 0); + } + glPopMatrix(); + } + + void debugLights() { + glDisable(GL_TEXTURE_2D); + glPointSize(8); + glBegin(GL_POINTS); + for (int i = 0; i < rooms.count; i++) + for (int j = 0; j < rooms[i]->lights.count; j++) { + auto &l = rooms[i]->lights[j]; + float a = l.Intensity1 / 8191.0f; + glColor3f(a, a, a); + glVertex3f(-l.x * SCALE, -l.y * SCALE, l.z * SCALE); + } + glEnd(); + glEnable(GL_TEXTURE_2D); + } + + void debugEntity() { + Core::setCulling(cfNone); + Core::active.shader = NULL; + glUseProgram(0); + + mat4 mProj; + glGetFloatv(GL_PROJECTION_MATRIX, (GLfloat*)&mProj); + + glPushMatrix(); + glScalef(-SCALE, -SCALE, SCALE); + + for (int i = 0; i < entities.count; i++) { + tr_entity *entity = &entities[i]; + + glPushMatrix(); + glTranslatef(entity->x, entity->y, entity->z); + + for (int i = 0; i < models.count; i++) + if (entity->id == models[i].id) { + glRotatef((entity->rotation >> 14) * 90.0f, 0, 1, 0); + tr_anim_frame *frame = (tr_anim_frame*)&frames[models[i].frame >> 1]; + glTranslatef(frame->x, frame->y, frame->z); + break; + } + + mat4 mView, mViewProj; + glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat*)&mView); + mViewProj = mProj * mView; + vec4 p = mViewProj * vec4(0, 0, 0, 1); + if (p.w > 0) { + p.xyz /= p.w; + p.y = -p.y; + + p.xy = (p.xy * 0.5f + 0.5f) * vec2(Core::width, Core::height); + char buf[16]; + sprintf(buf, "%d", entity->id); + + UI::begin(); + font->print(p.xy, vec4(1, 0, 0, 1), buf); + UI::end(); + } + + glPopMatrix(); + } + glPopMatrix(); + + Core::setCulling(cfFront); + Core::active.shader = NULL; + glUseProgram(0); + } + + bool isInsideRoom(const vec3 pos, tr_room *room) { + vec3 min = vec3(room->info.x, room->info.yTop, room->info.z); + Box box(min, min + vec3(room->xSectors * 1024, room->info.yBottom - room->info.yTop, room->zSectors * 1024)); + return box.intersect(vec3(-pos.x, -pos.y, pos.z) / SCALE); + } + */ + + tr_staticmesh* getMeshByID(int ID) { + for (int i = 0; i < staticMeshes.count; i++) + if (staticMeshes[i].ID == ID) + return &staticMeshes[i]; + return NULL; + } + + void setTexture(int objTexture) { + auto &t = objectTextures[objTexture]; + Core::setBlending(t.attribute == 2 ? bmAdd : bmAlpha); + textures[t.tileAndFlag & 0x7FFF]->bind(0); + } + + float tickTextureAnimation = 0.0f; + + void update() { + lara->update(); + + if (tickTextureAnimation > 0.25f) { + tickTextureAnimation = 0.0f; + + if (animatedTextures.count) { + uint16 *ptr = &animatedTextures[0]; + int count = *ptr++; + for (int i = 0; i < count; i++) { + auto animTex = (tr_anim_texture*)ptr; + auto id = objectTextures[animTex->textures[0]]; + for (int j = 0; j < animTex->tCount; j++) // tCount = count of textures in animation group - 1 (!!!) + objectTextures[animTex->textures[j]] = objectTextures[animTex->textures[j + 1]]; + objectTextures[animTex->textures[animTex->tCount]] = id; + ptr += (sizeof(tr_anim_texture) + sizeof(animTex->textures[0]) * (animTex->tCount + 1)) / sizeof(uint16); + } + } + } else + tickTextureAnimation += Core::deltaTime; + } + + void render() { + glEnable(GL_ALPHA_TEST); + glAlphaFunc(GL_GREATER, 0.9f); + glEnable(GL_TEXTURE_2D); + glEnable(GL_NORMALIZE); + glEnable(GL_COLOR_MATERIAL); + glEnable(GL_LIGHT0); + + + Core::setCulling(cfFront); + glColor3f(1, 1, 1); + + for (int i = 0; i < rooms.count; i++) + renderRoom(rooms[i]); + + for (int i = 0; i < entities.count; i++) + renderEntity(&entities[i]); + + // debugRooms(); + // debugMeshes(); + // debugLights(); + // debugPortals(); + // debugEntity(); + } +}; + +#pragma pack(pop) + +struct Camera { + float fov, znear, zfar; + vec3 pos, angle; + + void update() { + vec3 dir = vec3(sinf(angle.y - PI) * cosf(-angle.x), -sinf(-angle.x), cosf(angle.y - PI) * cosf(-angle.x)); + vec3 v = vec3(0); + + if (Input::down[ikW]) v = v + dir; + if (Input::down[ikS]) v = v - dir; + if (Input::down[ikD]) v = v + dir.cross(vec3(0, 1, 0)); + if (Input::down[ikA]) v = v - dir.cross(vec3(0, 1, 0)); + + pos = pos + v.normal() * (Core::deltaTime); + + if (Input::down[ikMouseL]) { + vec2 delta = Input::mouse.pos - Input::mouse.start.L; + angle.x -= delta.y * 0.01f; + angle.y -= delta.x * 0.01f; + angle.x = min(max(angle.x, -PI * 0.5f + EPS), PI * 0.5f - EPS); + Input::mouse.start.L = Input::mouse.pos; + } + + } + + void setup() { + Core::mView.identity(); + Core::mView.rotateZ(-angle.z); + Core::mView.rotateX(-angle.x); + Core::mView.rotateY(-angle.y); + Core::mView.translate(vec3(-pos.x, -pos.y, -pos.z)); + + Core::mProj = mat4(fov, (float)Core::width / (float)Core::height, znear, zfar); + + Core::mViewProj = Core::mProj * Core::mView; + + glMatrixMode(GL_PROJECTION); + glLoadMatrixf((GLfloat*)&Core::mProj); + glMatrixMode(GL_MODELVIEW); + glLoadMatrixf((GLfloat*)&Core::mView); + } +}; + +namespace Game { + tr_level *level; + Camera camera; + + void init() { + Core::init(); + + Stream stream("data\\LEVEL3A.PHD"); + // Stream stream("GYM.PHD"); + level = new tr_level(&stream); + + camera.fov = 90.0f; + camera.znear = 0.1f; + camera.zfar = 1000.0f; +// camera.pos = vec3(-10, -2, 26); + camera.pos = vec3(-13.25, 0.42, 38.06); +// camera.pos = vec3(-36, -1, 2); + camera.angle = vec3(0); + } + + void free() { + delete level; + + Core::free(); + } + + void update() { + camera.update(); + time += Core::deltaTime; + + level->update(); + } + + void render() { +// Core::clear(vec4(0.1f, 0.4f, 0.4f, 0.0)); + Core::clear(vec4(0.0f, 0.0f, 0.0f, 0.0)); + Core::setViewport(0, 0, Core::width, Core::height); + Core::setBlending(bmAlpha); + + camera.setup(); + /* + Debug::Draw::begin(); + glDisable(GL_TEXTURE_2D); + Debug::Draw::axes(10); + glEnable(GL_TEXTURE_2D); + */ + vec3 pos = Core::mView.inverse().getPos(); + GLfloat p[4] = { pos.x, pos.y, pos.z, 1.0f }; + glLightfv(GL_LIGHT0, GL_POSITION, p); + glEnable(GL_DEPTH_TEST); + + level->render(); + + //Debug::Draw::end(); + } + + /* + void input(InputKey key, InputState state) { + static vec2 mLast; + if (state == isDown && key == ikMouseL) { + mLast = Input::mouse.pos; + return; + } + + if (state == isMove && key == ikMouse && (Input::mouse.L.down || Input::mouse.R.down)) { + vec2 delta = Input::mouse.pos - mLast; + camera.angle.x -= delta.y * 0.01f; + camera.angle.y -= delta.x * 0.01f; + camera.angle.x = _min(_max(camera.angle.x, -PI * 0.5f + EPS), PI * 0.5f - EPS); + mLast = Input::mouse.pos; + } + } + */ +} + +#endif \ No newline at end of file diff --git a/src/input.h b/src/input.h new file mode 100644 index 0000000..41a4b0e --- /dev/null +++ b/src/input.h @@ -0,0 +1,83 @@ +#ifndef H_INPUT +#define H_INPUT + +#include "utils.h" + +enum InputKey { ikNone, + // keyboard + ikLeft, ikRight, ikUp, ikDown, ikSpace, ikEnter, ikEscape, ikShift, ikCtrl, ikAlt, + ik0, ik1, ik2, ik3, ik4, ik5, ik6, ik7, ik8, ik9, + ikA, ikB, ikC, ikD, ikE, ikF, ikG, ikH, ikI, ikJ, ikK, ikL, ikM, + ikN, ikO, ikP, ikQ, ikR, ikS, ikT, ikU, ikV, ikW, ikX, ikY, ikZ, + // mouse + ikMouseL, ikMouseR, ikMouseM, + // touch + ikTouchA, ikTouchB, + // gamepad + ikJoyA, ikJoyB, ikJoyX, ikJoyY, ikJoyLB, ikJoyRB, ikJoyL, ikJoyR, ikJoySelect, ikJoyStart, ikJoyLT, ikJoyRT, ikJoyDP, + ikMAX }; + +namespace Input { + + bool down[ikMAX]; + + struct { + vec2 pos; + struct { + vec2 L, R, M; + } start; + } mouse; + + struct { + vec2 L, R; + float LT, RT, DP; + } joy; + + struct { + vec2 A, B; + + struct { + vec2 A, B; + } start; + } touch; + + void reset() { + memset(down, 0, sizeof(down)); + memset(&mouse, 0, sizeof(mouse)); + memset(&joy, 0, sizeof(joy)); + memset(&touch, 0, sizeof(touch)); + } + + void setDown(InputKey key, bool value) { + if (down[key] == value) + return; + + if (value) + switch (key) { + case ikMouseL : mouse.start.L = mouse.pos; break; + case ikMouseR : mouse.start.R = mouse.pos; break; + case ikMouseM : mouse.start.M = mouse.pos; break; + case ikTouchA : touch.start.A = touch.A; break; + case ikTouchB : touch.start.B = touch.B; break; + } + + down[key] = value; + } + + void setPos(InputKey key, const vec2 &pos) { + switch (key) { + case ikMouseL : + case ikMouseR : + case ikMouseM : mouse.pos = pos; break; + case ikJoyL : joy.L = pos; break; + case ikJoyR : joy.R = pos; break; + case ikJoyLT : joy.LT = pos.x; break; + case ikJoyRT : joy.RT = pos.x; break; + case ikJoyDP : joy.DP = pos.x; break; + case ikTouchA : touch.A = pos; break; + case ikTouchB : touch.B = pos; break; + } + } +} + +#endif \ No newline at end of file diff --git a/src/mesh.h b/src/mesh.h new file mode 100644 index 0000000..2d77aec --- /dev/null +++ b/src/mesh.h @@ -0,0 +1,62 @@ +#ifndef H_MESH +#define H_MESH + +#include "core.h" + +typedef unsigned short Index; + +struct Vertex { + vec3 coord; + vec3 normal; + vec2 texCoord; +}; + +struct Mesh { + GLuint ID[2]; + int iCount; + int vCount; + + Mesh(const char *name) { + Stream stream(name); + Index *indices = stream.readArray (stream.read(iCount)); + Vertex *vertices = stream.readArray(stream.read(vCount)); + + glGenBuffers(2, ID); + bind(); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, iCount * sizeof(Index), indices, GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, vCount * sizeof(Vertex), vertices, GL_STATIC_DRAW); + + delete[] indices; + delete[] vertices; + } + + virtual ~Mesh() { + glDeleteBuffers(2, ID); + } + + void bind() { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ID[0]); + glBindBuffer(GL_ARRAY_BUFFER, ID[1]); + } + + void render() { + bind(); + + glEnableVertexAttribArray(aCoord); + glEnableVertexAttribArray(aNormal); + glEnableVertexAttribArray(aTexCoord); + + + Vertex *v = NULL; + glVertexAttribPointer(aCoord, 3, GL_FLOAT, false, sizeof(Vertex), &v->coord); + glVertexAttribPointer(aNormal, 3, GL_FLOAT, false, sizeof(Vertex), &v->normal); + glVertexAttribPointer(aTexCoord, 2, GL_FLOAT, false, sizeof(Vertex), &v->texCoord); + glDrawElements(GL_TRIANGLES, iCount, GL_UNSIGNED_SHORT, NULL); + + glDisableVertexAttribArray(aCoord); + glDisableVertexAttribArray(aNormal); + glDisableVertexAttribArray(aTexCoord); + } +}; + +#endif \ No newline at end of file diff --git a/src/shader.h b/src/shader.h new file mode 100644 index 0000000..9901496 --- /dev/null +++ b/src/shader.h @@ -0,0 +1,91 @@ +#ifndef H_SHADER +#define H_SHADER + +#include "core.h" + +enum AttribType { aCoord, aTexCoord, aNormal, aMAX }; +enum SamplerType { sTex0, sMAX }; +enum UniformType { uViewProj, uModel, uLightVec, uMAX }; + +const char *AttribName[aMAX] = { "aCoord", "aTexCoord", "aNormal" }; +const char *SamplerName[sMAX] = { "sTex0" }; +const char *UniformName[uMAX] = { "uViewProj", "uModel", "uLightVec" }; + +struct Shader { + GLuint ID; + GLint uID[uMAX]; + + Shader(const char *name, int param) { + Stream stream(name); + + char *text = new char[stream.size + 1]; + stream.read(text, stream.size); + text[stream.size] = '\0'; + + #define GLSL_DEFINE "#version 110\n" + + const int type[2] = { GL_VERTEX_SHADER, GL_FRAGMENT_SHADER }; + const char *code[2][2] = { + { GLSL_DEFINE "#define VERTEX\n", text }, + { GLSL_DEFINE "#define FRAGMENT\n", text } + }; + + GLchar info[256]; + + ID = glCreateProgram(); + for (int i = 0; i < 2; i++) { + GLuint obj = glCreateShader(type[i]); + glShaderSource(obj, 2, code[i], NULL); + glCompileShader(obj); + + glGetShaderInfoLog(obj, sizeof(info), NULL, info); + if (info[0]) LOG("! shader: %s\n", info); + + glAttachShader(ID, obj); + glDeleteShader(obj); + } + delete[] text; + + for (int at = 0; at < aMAX; at++) + glBindAttribLocation(ID, at, AttribName[at]); + + glLinkProgram(ID); + + glGetProgramInfoLog(ID, sizeof(info), NULL, info); + if (info[0]) LOG("! program: %s\n", info); + + bind(); + for (int st = 0; st < sMAX; st++) + glUniform1iv(glGetUniformLocation(ID, (GLchar*)SamplerName[st]), 1, &st); + + for (int ut = 0; ut < uMAX; ut++) + uID[ut] = glGetUniformLocation(ID, (GLchar*)UniformName[ut]); + } + + virtual ~Shader() { + glDeleteProgram(ID); + } + + void bind() { + glUseProgram(ID); + setParam(uViewProj, Core::mViewProj); + setParam(uModel, Core::mModel); + } + + void setParam(UniformType uType, const vec3 &value, int count = 1) { + if (uID[uType] != -1) + glUniform3fv(uID[uType], count, (GLfloat*)&value); + } + + void setParam(UniformType uType, const vec4 &value, int count = 1) { + if (uID[uType] != -1) + glUniform4fv(uID[uType], count, (GLfloat*)&value); + } + + void setParam(UniformType uType, const mat4 &value, int count = 1) { + if (uID[uType] != -1) + glUniformMatrix4fv(uID[uType], count, false, (GLfloat*)&value); + } +}; + +#endif \ No newline at end of file diff --git a/src/texture.h b/src/texture.h new file mode 100644 index 0000000..b96f9df --- /dev/null +++ b/src/texture.h @@ -0,0 +1,103 @@ +#ifndef H_TEXTURE +#define H_TEXTURE + +#include "core.h" + +#define PVR_RGBA8 0x61626772 +#define PVR_ALPHA 0x00000061 +#define PVR_PVRTC4 0x00000002 // iOS +#define PVR_ETC1 0x00000006 // Android +#define PVR_BC1 0x00000007 // Desktop + +#define GL_COMPRESSED_RGB_S3TC_DXT1 0x83F0 +#define GL_ETC1_RGB8_OES 0x8D64 +#define GL_COMPRESSED_RGB_PVRTC_4BPPV1 0x8C00 + +struct Texture { + GLuint ID; + int width, height; + + Texture(const char *name) { + Stream stream(name); + + struct { + int version; + int flags; + long format; + long ext; + int color; + int channel; + int height; + int width; + int depth; + int surfaces; + int faces; + int mipCount; + int metaSize; + } header; + + stream.read(header); + stream.seek(header.metaSize); + + GLenum fmt; + int minSize; + switch (header.format) { + case PVR_ALPHA : minSize = 1; fmt = GL_ALPHA; break; + case PVR_RGBA8 : minSize = 1; fmt = GL_RGBA; break; + case PVR_BC1 : minSize = 4; fmt = GL_COMPRESSED_RGB_S3TC_DXT1; break; + case PVR_ETC1 : minSize = 4; fmt = GL_ETC1_RGB8_OES; break; + case PVR_PVRTC4 : minSize = 8; fmt = GL_COMPRESSED_RGB_PVRTC_4BPPV1; break; + default : + LOG("! unsupported texture format\n"); + ID = 0; + return; + } + + glGenTextures(1, &ID); + bind(0); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, header.mipCount == 1 ? GL_LINEAR : GL_LINEAR_MIPMAP_LINEAR); + + int sizeX = width = header.width; + int sizeY = height = header.height; + + char *data = new char[width * height * 4]; + for (int i = 0; i < header.mipCount; i++) { + if (minSize == 1) { + int size = sizeX * sizeY * (header.format == PVR_RGBA8 ? 4 : 1); + stream.read(data, size); + glTexImage2D(GL_TEXTURE_2D, i, fmt, sizeX, sizeY, 0, fmt, GL_UNSIGNED_BYTE, data); + } else { + int size = (max(sizeX, minSize) * max(sizeY, minSize) * 4 + 7) / 8; + stream.read(data, size); + glCompressedTexImage2D(GL_TEXTURE_2D, i, fmt, sizeX, sizeY, 0, size, data); + } + sizeX /= 2; + sizeY /= 2; + } + + delete[] data; + } + + Texture(int width, int height, int format, void *data) : width(width), height(height) { + glGenTextures(1, &ID); + bind(0); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + } + + virtual ~Texture() { + glDeleteTextures(1, &ID); + } + + void bind(int sampler) { + glActiveTexture(GL_TEXTURE0 + sampler); + glBindTexture(GL_TEXTURE_2D, ID); + } +}; + +#endif \ No newline at end of file diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..21113f0 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,540 @@ +#ifndef H_UTILS +#define H_UTILS + +#include +#include +#include + +#ifdef _DEBUG + #define debugBreak() _asm { int 3 } + #define ASSERT(expr) if (expr) {} else { LOG("ASSERT %s in %s:%d\n", #expr, __FILE__, __LINE__); debugBreak(); } +#else + #define ASSERT(expr) +#endif + +#ifndef ANDROID + #define LOG(...) printf(__VA_ARGS__) +#else + #include + #define LOG(...) __android_log_print(ANDROID_LOG_INFO,"X5",__VA_ARGS__) +#endif + +#define PI 3.14159265358979323846f +#define DEG2RAD (PI / 180.0f) +#define RAD2DEG (180.0f / PI) +#define EPS FLT_EPSILON + +template +inline const T& min(const T &a, const T &b) { + return a < b ? a : b; +} + +template +inline const T& max(const T &a, const T &b) { + return a > b ? a : b; +} + +struct vec2 { + float x, y; + vec2() {} + vec2(float s) : x(s), y(s) {} + vec2(float x, float y) : x(x), y(y) {} + + vec2 operator + (const vec2 &v) const { return vec2(x+v.x, y+v.y); } + vec2 operator - (const vec2 &v) const { return vec2(x-v.x, y-v.y); } + vec2 operator * (float s) const { return vec2(x*s, y*s); } + float dot(const vec2 &v) const { return x*v.x + y*v.y; } + float cross(const vec2 &v) const { return x*v.y - y*v.x; } + float length() const { return sqrtf(dot(*this)); } + vec2 normal() const { float s = length(); return s == 0.0 ? (*this) : (*this)*(1.0f/s); } +}; + +struct vec3 { + float x, y, z; + vec3() {} + vec3(float s) : x(s), y(s), z(s) {} + vec3(float x, float y, float z) : x(x), y(y), z(z) {} + vec3(const vec2 &xy, float z = 0.0f) : x(xy.x), y(xy.y), z(z) {} + + vec3 operator + (const vec3 &v) const { return vec3(x+v.x, y+v.y, z+v.z); } + vec3 operator - (const vec3 &v) const { return vec3(x-v.x, y-v.y, z-v.z); } + vec3 operator * (const vec3 &v) const { return vec3(x*v.x, y*v.y, z*v.z); } + vec3 operator * (float s) const { return vec3(x*s, y*s, z*s); } + + + float dot(const vec3 &v) const { return x*v.x + y*v.y + z*v.z; } + vec3 cross(const vec3 &v) const { return vec3(y*v.z - z*v.y, z*v.x - x*v.z, x*v.y - y*v.x); } + float length() const { return sqrtf(x*x + y*y + z*z); } + vec3 normal() const { float s = length(); return s == 0.0 ? (*this) : (*this)*(1.0f/s); } + + vec3 lerp(const vec3 &v, const float t) const { + return *this + (v - *this) * t; + } +}; + +struct vec4 { + union { + struct { vec3 xyz; }; + struct { float x, y, z, w; }; + }; + + vec4() {} + vec4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {} +}; + +struct quat { + float x, y, z, w; + + quat() {} + quat(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {} + + quat(const vec3 &axis, float angle) { + angle *= 0.5f; + float s = sinf(angle); + x = axis.x * s; + y = axis.y * s; + z = axis.z * s; + w = cosf(angle); + } + + quat quat::operator - () const { + return quat(-x, -y, -z, -w); + } + + quat quat::operator + (const quat &q) const { + return quat(x + q.x, y + q.y, z + q.z, w + q.w); + } + + quat quat::operator - (const quat &q) const { + return quat(x - q.x, y - q.y, z - q.z, w - q.w); + } + + quat quat::operator * (const float s) const { + return quat(x * s, y * s, z * s, w * s); + } + + quat quat::operator * (const quat &q) const { + return quat(w * q.x + x * q.w + y * q.z - z * q.y, + w * q.y + y * q.w + z * q.x - x * q.z, + w * q.z + z * q.w + x * q.y - y * q.x, + w * q.w - x * q.x - y * q.y - z * q.z); + } + + float dot(const quat &q) const { + return x * q.x + y * q.y + z * q.z + w * q.w; + } + + float length2() const { + return dot(*this); + } + + float length() const { + return sqrtf(length2()); + } + + void quat::normalize() { + *this = normal(); + } + + quat quat::normal() const { + return *this * (1.0f / length()); + } + + quat quat::conjugate() const { + return quat(-x, -y, -z, w); + } + + quat quat::inverse() const { + return conjugate() * (1.0f / length2()); + } + + quat lerp(const quat &q, float t) const { + if (t <= 0.0f) return *this; + if (t >= 1.0f) return q; + + return dot(q) < 0 ? (*this - (q + *this) * t) : + (*this + (q - *this) * t); + } + + quat slerp(const quat &q, float t) const { + if (t <= 0.0f) return *this; + if (t >= 1.0f) return q; + + quat temp; + float omega, cosom, sinom, scale0, scale1; + + cosom = dot(q); + if (cosom < 0.0f) { + temp = -q; + cosom = -cosom; + } else + temp = q; + + if (1.0f - cosom > EPS) { + omega = acos(cosom); + sinom = 1.0f / sin(omega); + scale0 = sin((1.0f - t) * omega) * sinom; + scale1 = sin(t * omega) * sinom; + } else { + scale0 = 1.0f - t; + scale1 = t; + } + + return *this * scale0 + temp * scale1; + } +}; + +struct mat4 { + + union { + struct { + float e00, e10, e20, e30, + e01, e11, e21, e31, + e02, e12, e22, e32, + e03, e13, e23, e33; + }; + struct { vec4 right, up, dir, offset; }; + }; + + mat4() {} + + mat4(const quat &rot, const vec3 &pos) { + setRot(rot); + setPos(pos); + e30 = e31 = e32 = 0.0f; + e33 = 1.0f; + } + + mat4(float l, float r, float b, float t, float znear, float zfar) { + identity(); + e00 = 2.0f / (r - l); + e11 = 2.0f / (t - b); + e22 = 2.0f / (znear - zfar); + e03 = (l + r) / (l - r); + e13 = (b + t) / (b - t); + e23 = (zfar + znear) / (znear - zfar); + } + + mat4(float fov, float aspect, float znear, float zfar) { + float k = 1.0f / tanf(fov * 0.5f * DEG2RAD); + identity(); + e00 = k / aspect; + e11 = k; + e22 = (znear + zfar) / (znear - zfar); + e33 = 0.0f; + e32 = -1.0f; + e23 = 2.0f * zfar * znear / (znear - zfar); + } + + void identity() { + e10 = e20 = e30 = e01 = e21 = e31 = e02 = e12 = e32 = e03 = e13 = e23 = 0.0f; + e00 = e11 = e22 = e33 = 1.0f; + } + + mat4 operator * (const mat4 &m) const { + mat4 r; + r.e00 = e00 * m.e00 + e01 * m.e10 + e02 * m.e20 + e03 * m.e30; + r.e10 = e10 * m.e00 + e11 * m.e10 + e12 * m.e20 + e13 * m.e30; + r.e20 = e20 * m.e00 + e21 * m.e10 + e22 * m.e20 + e23 * m.e30; + r.e30 = e30 * m.e00 + e31 * m.e10 + e32 * m.e20 + e33 * m.e30; + r.e01 = e00 * m.e01 + e01 * m.e11 + e02 * m.e21 + e03 * m.e31; + r.e11 = e10 * m.e01 + e11 * m.e11 + e12 * m.e21 + e13 * m.e31; + r.e21 = e20 * m.e01 + e21 * m.e11 + e22 * m.e21 + e23 * m.e31; + r.e31 = e30 * m.e01 + e31 * m.e11 + e32 * m.e21 + e33 * m.e31; + r.e02 = e00 * m.e02 + e01 * m.e12 + e02 * m.e22 + e03 * m.e32; + r.e12 = e10 * m.e02 + e11 * m.e12 + e12 * m.e22 + e13 * m.e32; + r.e22 = e20 * m.e02 + e21 * m.e12 + e22 * m.e22 + e23 * m.e32; + r.e32 = e30 * m.e02 + e31 * m.e12 + e32 * m.e22 + e33 * m.e32; + r.e03 = e00 * m.e03 + e01 * m.e13 + e02 * m.e23 + e03 * m.e33; + r.e13 = e10 * m.e03 + e11 * m.e13 + e12 * m.e23 + e13 * m.e33; + r.e23 = e20 * m.e03 + e21 * m.e13 + e22 * m.e23 + e23 * m.e33; + r.e33 = e30 * m.e03 + e31 * m.e13 + e32 * m.e23 + e33 * m.e33; + return r; + } + + vec3 operator * (const vec3 &v) const { + return vec3( + e00 * v.x + e01 * v.y + e02 * v.z + e03, + e10 * v.x + e11 * v.y + e12 * v.z + e13, + e20 * v.x + e21 * v.y + e22 * v.z + e23); + } + + vec4 operator * (const vec4 &v) const { + return vec4( + e00 * v.x + e01 * v.y + e02 * v.z + e03 * v.w, + e10 * v.x + e11 * v.y + e12 * v.z + e13 * v.w, + e20 * v.x + e21 * v.y + e22 * v.z + e23 * v.w, + e30 * v.x + e31 * v.y + e32 * v.z + e33 * v.w); + } + + void translate(const vec3 &offset) { + mat4 m; + m.identity(); + m.setPos(offset); + *this = *this * m; + }; + + void scale(const vec3 &factor) { + mat4 m; + m.identity(); + m.e00 = factor.x; + m.e11 = factor.y; + m.e22 = factor.z; + *this = *this * m; + } + + void rotateX(float angle) { + mat4 m; + m.identity(); + float s = sinf(angle), c = cosf(angle); + m.e11 = c; m.e21 = s; + m.e12 = -s; m.e22 = c; + *this = *this * m; + } + + void rotateY(float angle) { + mat4 m; + m.identity(); + float s = sinf(angle), c = cosf(angle); + m.e00 = c; m.e20 = -s; + m.e02 = s; m.e22 = c; + *this = *this * m; + } + + void rotateZ(float angle) { + mat4 m; + m.identity(); + float s = sinf(angle), c = cosf(angle); + m.e00 = c; m.e01 = -s; + m.e10 = s; m.e11 = c; + *this = *this * m; + } + + float det() const { + return e00 * (e11 * (e22 * e33 - e32 * e23) - e21 * (e12 * e33 - e32 * e13) + e31 * (e12 * e23 - e22 * e13)) - + e10 * (e01 * (e22 * e33 - e32 * e23) - e21 * (e02 * e33 - e32 * e03) + e31 * (e02 * e23 - e22 * e03)) + + e20 * (e01 * (e12 * e33 - e32 * e13) - e11 * (e02 * e33 - e32 * e03) + e31 * (e02 * e13 - e12 * e03)) - + e30 * (e01 * (e12 * e23 - e22 * e13) - e11 * (e02 * e23 - e22 * e03) + e21 * (e02 * e13 - e12 * e03)); + } + + mat4 inverse() const { + float idet = 1.0f / det(); + mat4 r; + r.e00 = (e11 * (e22 * e33 - e32 * e23) - e21 * (e12 * e33 - e32 * e13) + e31 * (e12 * e23 - e22 * e13)) * idet; + r.e01 = -(e01 * (e22 * e33 - e32 * e23) - e21 * (e02 * e33 - e32 * e03) + e31 * (e02 * e23 - e22 * e03)) * idet; + r.e02 = (e01 * (e12 * e33 - e32 * e13) - e11 * (e02 * e33 - e32 * e03) + e31 * (e02 * e13 - e12 * e03)) * idet; + r.e03 = -(e01 * (e12 * e23 - e22 * e13) - e11 * (e02 * e23 - e22 * e03) + e21 * (e02 * e13 - e12 * e03)) * idet; + r.e10 = -(e10 * (e22 * e33 - e32 * e23) - e20 * (e12 * e33 - e32 * e13) + e30 * (e12 * e23 - e22 * e13)) * idet; + r.e11 = (e00 * (e22 * e33 - e32 * e23) - e20 * (e02 * e33 - e32 * e03) + e30 * (e02 * e23 - e22 * e03)) * idet; + r.e12 = -(e00 * (e12 * e33 - e32 * e13) - e10 * (e02 * e33 - e32 * e03) + e30 * (e02 * e13 - e12 * e03)) * idet; + r.e13 = (e00 * (e12 * e23 - e22 * e13) - e10 * (e02 * e23 - e22 * e03) + e20 * (e02 * e13 - e12 * e03)) * idet; + r.e20 = (e10 * (e21 * e33 - e31 * e23) - e20 * (e11 * e33 - e31 * e13) + e30 * (e11 * e23 - e21 * e13)) * idet; + r.e21 = -(e00 * (e21 * e33 - e31 * e23) - e20 * (e01 * e33 - e31 * e03) + e30 * (e01 * e23 - e21 * e03)) * idet; + r.e22 = (e00 * (e11 * e33 - e31 * e13) - e10 * (e01 * e33 - e31 * e03) + e30 * (e01 * e13 - e11 * e03)) * idet; + r.e23 = -(e00 * (e11 * e23 - e21 * e13) - e10 * (e01 * e23 - e21 * e03) + e20 * (e01 * e13 - e11 * e03)) * idet; + r.e30 = -(e10 * (e21 * e32 - e31 * e22) - e20 * (e11 * e32 - e31 * e12) + e30 * (e11 * e22 - e21 * e12)) * idet; + r.e31 = (e00 * (e21 * e32 - e31 * e22) - e20 * (e01 * e32 - e31 * e02) + e30 * (e01 * e22 - e21 * e02)) * idet; + r.e32 = -(e00 * (e11 * e32 - e31 * e12) - e10 * (e01 * e32 - e31 * e02) + e30 * (e01 * e12 - e11 * e02)) * idet; + r.e33 = (e00 * (e11 * e22 - e21 * e12) - e10 * (e01 * e22 - e21 * e02) + e20 * (e01 * e12 - e11 * e02)) * idet; + return r; + } + + mat4 transpose() const { + mat4 r; + r.e00 = e00; r.e10 = e01; r.e20 = e02; r.e30 = e03; + r.e01 = e10; r.e11 = e11; r.e21 = e12; r.e31 = e13; + r.e02 = e20; r.e12 = e21; r.e22 = e22; r.e32 = e23; + r.e03 = e30; r.e13 = e31; r.e23 = e32; r.e33 = e33; + return r; + } + + quat mat4::getRot() const { + float t, s; + t = 1.0f + e00 + e11 + e22; + if (t > EPS) { + s = 0.5f / sqrtf(t); + return quat((e21 - e12) * s, (e02 - e20) * s, (e10 - e01) * s, 0.25f / s); + } else + if (e00 > e11 && e00 > e22) { + s = 0.5f / sqrtf(1.0f + e00 - e11 - e22); + return quat(0.25f / s, (e01 + e10) * s, (e02 + e20) * s, (e21 - e12) * s); + } else + if (e11 > e22) { + s = 0.5f / sqrtf(1.0f - e00 + e11 - e22); + return quat((e01 + e10) * s, 0.25f / s, (e12 + e21) * s, (e02 - e20) * s); + } else { + s = 0.5f / sqrtf(1.0f - e00 - e11 + e22); + return quat((e02 + e20) * s, (e12 + e21) * s, 0.25f / s, (e10 - e01) * s); + } + } + + void mat4::setRot(const quat &rot) { + float sx = rot.x * rot.x, + sy = rot.y * rot.y, + sz = rot.z * rot.z, + sw = rot.w * rot.w, + inv = 1.0f / (sx + sy + sz + sw); + + e00 = ( sx - sy - sz + sw) * inv; + e11 = (-sx + sy - sz + sw) * inv; + e22 = (-sx - sy + sz + sw) * inv; + inv *= 2.0f; + + float t1 = rot.x * rot.y; + float t2 = rot.z * rot.w; + e10 = (t1 + t2) * inv; + e01 = (t1 - t2) * inv; + + t1 = rot.x * rot.z; + t2 = rot.y * rot.w; + e20 = (t1 - t2) * inv; + e02 = (t1 + t2) * inv; + + t1 = rot.y * rot.z; + t2 = rot.x * rot.w; + e21 = (t1 + t2) * inv; + e12 = (t1 - t2) * inv; + } + + vec3 mat4::getPos() const { + return offset.xyz; + } + + void mat4::setPos(const vec3 &pos) { + offset.xyz = pos; + } +}; + +struct Stream { + FILE *f; + int size, pos; + + Stream(const char *name) : pos(0) { + f = fopen(name, "rb"); + fseek(f, 0, SEEK_END); + size = ftell(f); + fseek(f, 0, SEEK_SET); + } + + ~Stream() { + fclose(f); + } + + void setPos(int pos) { + this->pos = pos; + fseek(f, pos, SEEK_SET); + } + + void seek(int offset) { + fseek(f, offset, SEEK_CUR); + pos += offset; + } + + int read(void *data, int size) { + pos += size; + return fread(data, 1, size, f); + } + + template + T& read(T &x) { + read(&x, sizeof(x)); + return x; + } + + template + T* readArray(int count) { + if (!count) + return NULL; + T *a = new T[count]; + read(a, count * sizeof(T)); + return a; + } + + char *readStr(char *buffer) { + unsigned char len; + read(&len, sizeof(len)); + if (!len) + return NULL; + read(buffer, len); + buffer[len] = 0; + return buffer; + } +}; + + +template +struct Array { + T **items; + C count; + + Array() : items(NULL), count(0) {} + + Array(int count) : count(count) { + if (count == 0) { + items = NULL; + return; + } + items = new T*[count]; + for (int i = 0; i < count; i++) + items[i] = new T(); + } + + Array(Stream *stream) { + stream->read(count); + if (count == 0) { + items = NULL; + return; + } + items = new T*[count]; + for (int i = 0; i < count; i++) + items[i] = new T(stream); + } + + ~Array() { + for (int i = 0; i < count; i++) + delete items[i]; + delete[] items; + } + + T* operator[] (int index) const { + return items[index]; + } +}; + +template +struct Vector { + T *items; + C count; + + Vector(Stream *stream) { + stream->read(count); + if (count <= 0) { + items = NULL; + return; + } + items = (T*)malloc((int)count * sizeof(T)); + stream->read(items, (int)count * sizeof(T)); + } + + Vector(Stream *stream, int count) : count(count) { + if (count <= 0) { + items = NULL; + return; + } + items = (T*)malloc((int)count * sizeof(T)); + stream->read(items, (int)count * sizeof(T)); + } + + + ~Vector() { + if (items) free(items); + } + + T& operator[] (int index) const { + ASSERT(index >= 0); + ASSERT(index < count); + return items[index]; + } +}; + +#endif \ No newline at end of file diff --git a/src/win/OpenLara.sln b/src/win/OpenLara.sln new file mode 100644 index 0000000..b9ba704 --- /dev/null +++ b/src/win/OpenLara.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Express 2013 for Windows Desktop +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OpenLara", "OpenLara.vcxproj", "{6935E070-59B8-418A-9241-70BACB4217B5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|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}.Release|Win32.ActiveCfg = Release|Win32 + {6935E070-59B8-418A-9241-70BACB4217B5}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/win/OpenLara.vcxproj b/src/win/OpenLara.vcxproj new file mode 100644 index 0000000..974f8af --- /dev/null +++ b/src/win/OpenLara.vcxproj @@ -0,0 +1,108 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {6935E070-59B8-418A-9241-70BACB4217B5} + Win32Proj + OpenLara + + + + Application + true + v120_xp + NotSet + + + Application + false + v120_xp + true + NotSet + + + + + + + + + + + + + true + ..\..\bin\ + ..\;$(VC_IncludePath);$(WindowsSdk_71A_IncludePath); + + + false + ..\..\bin\ + false + ..\;$(VC_IncludePath);$(WindowsSdk_71A_IncludePath); + + + + + + Level3 + Disabled + NOMINMAX;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + Strict + + + Console + true + opengl32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + NOMINMAX;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + false + false + MultiThreaded + Strict + false + + + Console + false + true + true + opengl32.lib;winmm.lib;wcrt.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + true + false + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/win/main.cpp b/src/win/main.cpp new file mode 100644 index 0000000..0d1cf01 --- /dev/null +++ b/src/win/main.cpp @@ -0,0 +1,219 @@ +#include "game.h" + +DWORD getTime() { +#ifdef DEBUG + LARGE_INTEGER Freq, Count; + QueryPerformanceFrequency(&Freq); + QueryPerformanceCounter(&Count); + return (DWORD)(Count.QuadPart * 1000L / Freq.QuadPart); +#else + timeBeginPeriod(0); + return timeGetTime(); +#endif +} + +InputKey keyToInputKey(int code) { + int codes[] = { + VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN, VK_SPACE, VK_RETURN, VK_ESCAPE, VK_SHIFT, VK_CONTROL, VK_MENU, + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + }; + + for (int i = 0; i < sizeof(codes) / sizeof(codes[0]); i++) + if (codes[i] == code) + return (InputKey)(ikLeft + i); + return ikNone; +} + +InputKey mouseToInputKey(int msg) { + return (msg >= WM_LBUTTONDOWN || msg <= WM_LBUTTONDBLCLK) ? ikMouseL : + (msg >= WM_RBUTTONDOWN || msg <= WM_RBUTTONDBLCLK) ? ikMouseR : ikMouseM; +} + +#define JOY_DEAD_ZONE_STICK 0.3f +#define JOY_DEAD_ZONE_TRIGGER 0.01f + +bool joyReady; + +void joyInit() { + JOYINFOEX info; + info.dwSize = sizeof(info); + info.dwFlags = JOY_RETURNALL; + joyReady = joyGetPosEx(0, &info) == JOYERR_NOERROR; +} + +void joyFree() { + joyReady = false; + memset(&Input::joy, 0, sizeof(Input::joy)); + for (int ik = ikJoyA; ik <= ikJoyDP; ik++) + Input::down[ik] = false; +} + +float joyAxis(int x, int xMin, int xMax) { + return ((x - xMin) / (float)(xMax - xMin)) * 2.0f - 1.0f; +} + +vec2 joyDir(float ax, float ay) { + vec2 dir = vec2(ax, ay); + float dist = min(1.0f, dir.length()); + if (dist < JOY_DEAD_ZONE_STICK) dist = 0; + + return dir.normal() * dist; +} + +void joyUpdate() { + if (!joyReady) return; + + JOYINFOEX info; + info.dwSize = sizeof(info); + info.dwFlags = JOY_RETURNALL; + + if (joyGetPosEx(0, &info) == JOYERR_NOERROR) { + JOYCAPS caps; + joyGetDevCaps(0, &caps, sizeof(caps)); + + Input::setPos(ikJoyL, joyDir(joyAxis(info.dwXpos, caps.wXmin, caps.wXmax), + joyAxis(info.dwYpos, caps.wYmin, caps.wYmax))); + + if ((caps.wCaps & JOYCAPS_HASR) && (caps.wCaps & JOYCAPS_HASU)) + Input::setPos(ikJoyR, joyDir(joyAxis(info.dwUpos, caps.wUmin, caps.wUmax), + joyAxis(info.dwRpos, caps.wRmin, caps.wRmax))); + + if (caps.wCaps & JOYCAPS_HASZ) { + float z = joyAxis(info.dwZpos, caps.wZmin, caps.wZmax); + if (fabsf(z) > JOY_DEAD_ZONE_TRIGGER) + Input::setPos(z > 0.0f ? ikJoyLT : ikJoyRT, vec2(fabsf(z), 0.0f)); + } + + if (caps.wCaps & JOYCAPS_HASPOV && info.dwPOV != JOY_POVCENTERED) + Input::setPos(ikJoyDP, vec2((float)(1 + info.dwPOV / 4500), 0)); + + for (int i = 0; i < 10; i++) + Input::setDown((InputKey)(ikJoyA + i), (info.dwButtons & (1 << i)) > 0); + } else + joyFree(); +} + +static LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { + switch (msg) { + case WM_ACTIVATE : + Input::reset(); + break; + // keyboard + case WM_KEYDOWN : + case WM_KEYUP : + case WM_SYSKEYDOWN : + case WM_SYSKEYUP : + Input::setDown(keyToInputKey(wParam), msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); + break; + // mouse + case WM_LBUTTONDOWN : + case WM_LBUTTONUP : + case WM_LBUTTONDBLCLK : + case WM_RBUTTONDOWN : + case WM_RBUTTONUP : + case WM_RBUTTONDBLCLK : + case WM_MBUTTONDOWN : + case WM_MBUTTONUP : + case WM_MBUTTONDBLCLK : { + InputKey key = mouseToInputKey(msg); + Input::setPos(key, vec2((float)(short)LOWORD(lParam), (float)(short)HIWORD(lParam))); + bool down = msg != WM_LBUTTONUP && msg != WM_RBUTTONUP && msg != WM_MBUTTONUP; + Input::setDown(key, down); + if (down) + SetCapture(hWnd); + else + ReleaseCapture(); + break; + } + case WM_MOUSEMOVE : + Input::setPos(ikMouseL, vec2((float)(short)LOWORD(lParam), (float)(short)HIWORD(lParam))); + break; + // gamepad + case WM_DEVICECHANGE : + joyInit(); + return 1; + // touch + // ... + case WM_SIZE : + Core::width = LOWORD(lParam); + Core::height = HIWORD(lParam); + break; + case WM_DESTROY : + PostQuitMessage(0); + break; + default : + return DefWindowProc(hWnd, msg, wParam, lParam); + } + return 0; +} + +HGLRC initGL(HDC hDC) { + PIXELFORMATDESCRIPTOR pfd; + memset(&pfd, 0, sizeof(pfd)); + pfd.nSize = sizeof(pfd); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.cColorBits = 32; + pfd.cDepthBits = 24; + + int format = ChoosePixelFormat(hDC, &pfd); + SetPixelFormat(hDC, format, &pfd); + HGLRC hRC = wglCreateContext(hDC); + wglMakeCurrent(hDC, hRC); + return hRC; +} + +void freeGL(HGLRC hRC) { + wglMakeCurrent(0, 0); + wglDeleteContext(hRC); +} + +int main() { + RECT r = { 0, 0, 1280, 720 }; + AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, false); + + HWND hWnd = CreateWindow("static", "OpenLara", WS_OVERLAPPEDWINDOW, 0, 0, r.right - r.left, r.bottom - r.top, 0, 0, 0, 0); + + joyInit(); + + HDC hDC = GetDC(hWnd); + HGLRC hRC = initGL(hDC); + Game::init(); + + SetWindowLong(hWnd, GWL_WNDPROC, (LONG)&WndProc); + ShowWindow(hWnd, SW_SHOWDEFAULT); + + DWORD time, lastTime = getTime(); + + MSG msg; + msg.message = WM_PAINT; + + while (msg.message != WM_QUIT) + if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } else { + time = getTime(); + if (time <= lastTime) + continue; + + Core::deltaTime = (time - lastTime) * 0.001f; + lastTime = time; + + joyUpdate(); + Game::update(); + Game::render(); + + SwapBuffers(hDC); + } + + Game::free(); + freeGL(hRC); + ReleaseDC(hWnd, hDC); + + DestroyWindow(hWnd); + + return 0; +} \ No newline at end of file diff --git a/src/win/wcrt.lib b/src/win/wcrt.lib new file mode 100644 index 0000000..fce6632 Binary files /dev/null and b/src/win/wcrt.lib differ