From 3382830f9af62424806dd8efb4ac4f76d034fd98 Mon Sep 17 00:00:00 2001 From: XProger Date: Tue, 16 Aug 2016 01:13:15 +0300 Subject: [PATCH] - FFP renderer --- src/core.h | 149 ++++ src/game.h | 1639 ++++++++++++++++++++++++++++++++++++++ src/input.h | 83 ++ src/mesh.h | 62 ++ src/shader.h | 91 +++ src/texture.h | 103 +++ src/utils.h | 540 +++++++++++++ src/win/OpenLara.sln | 22 + src/win/OpenLara.vcxproj | 108 +++ src/win/main.cpp | 219 +++++ src/win/wcrt.lib | Bin 0 -> 124094 bytes 11 files changed, 3016 insertions(+) create mode 100644 src/core.h create mode 100644 src/game.h create mode 100644 src/input.h create mode 100644 src/mesh.h create mode 100644 src/shader.h create mode 100644 src/texture.h create mode 100644 src/utils.h create mode 100644 src/win/OpenLara.sln create mode 100644 src/win/OpenLara.vcxproj create mode 100644 src/win/main.cpp create mode 100644 src/win/wcrt.lib 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 0000000000000000000000000000000000000000..fce66328a5f97706fd62e2fe29c69d1e1fb8010b GIT binary patch literal 124094 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

GtB08EyAUASYG^4 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