From f36a4ddf2f3cfa470e7995fdbae77c47acdb5a25 Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Thu, 18 Jun 2020 21:31:52 -0700 Subject: [PATCH 01/10] Added initial code to display buildings from the remaster. --- ILU.dll | Bin 0 -> 100352 bytes code/CMakeLists.txt | 2 +- code/REDALERT/BDATA.CPP | 34 +++++++++++++++++++++++++++++++--- code/REDALERT/CONQUER.CPP | 4 ++-- code/REDALERT/Image.h | 2 ++ code/REDALERT/WINSTUB.CPP | 13 +++++++++---- code/external/devil/ILU.lib | Bin 0 -> 12006 bytes 7 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 ILU.dll create mode 100644 code/external/devil/ILU.lib diff --git a/ILU.dll b/ILU.dll new file mode 100644 index 0000000000000000000000000000000000000000..2966069e507cddfc4012eb04c50fe6973083c61e GIT binary patch literal 100352 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 literal 0 HcmV?d00001 diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 4bfc44d..525bad0 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -715,4 +715,4 @@ add_executable(RedAlert ${src_win32lib} ${src_redalert} ${src_external}) set_target_properties(RedAlert PROPERTIES OUTPUT_NAME "RedAlert" LINK_FLAGS "/PDB:\"RedAlert.pdb\" /SUBSYSTEM:WINDOWS" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/../" ) target_include_directories(RedAlert PRIVATE ./external/devil/;./external/;./redalert/win32lib;./external/lua/;./external/imgui;./external/dxsdk/Include;./external/ffmpeg-win32/include;./external/sdl2/include;./win32lib;./external/openal/include) -target_link_libraries(RedAlert "opengl32.lib" "winmm.lib" "Ws2_32.lib" "${CMAKE_SOURCE_DIR}/external/devil/DevIL.lib" "${CMAKE_SOURCE_DIR}/external/sdl2/lib/x86/SDL2.lib" "${CMAKE_SOURCE_DIR}/external/openal/out/build/x86-Release/OpenAL32.lib") +target_link_libraries(RedAlert "opengl32.lib" "winmm.lib" "Ws2_32.lib" "${CMAKE_SOURCE_DIR}/external/devil/ilu.lib" "${CMAKE_SOURCE_DIR}/external/devil/DevIL.lib" "${CMAKE_SOURCE_DIR}/external/sdl2/lib/x86/SDL2.lib" "${CMAKE_SOURCE_DIR}/external/openal/out/build/x86-Release/OpenAL32.lib") diff --git a/code/REDALERT/BDATA.CPP b/code/REDALERT/BDATA.CPP index 2aa0319..e452ce0 100644 --- a/code/REDALERT/BDATA.CPP +++ b/code/REDALERT/BDATA.CPP @@ -3140,6 +3140,19 @@ void BuildingTypeClass::One_Time(void) */ _makepath(fullname, NULL, NULL, building.Graphic_Name(), ".SHP"); ((void const *&)building.ImageData) = MFCD::Retrieve(fullname); + { + char imageFileName[512]; + sprintf(imageFileName, "DATA/ART/TEXTURES/SRGB/RED_ALERT/STRUCTURES/%s/%s-0000.TGA", building.Graphic_Name(), building.Graphic_Name()); + ((void const*&)building.HDImageData) = Image_LoadImage(imageFileName); + + if (building.ImageData != NULL && building.HDImageData != NULL) { + int width = Get_Build_Frame_Width(building.ImageData); + int height = Get_Build_Frame_Height(building.ImageData); + + building.HDImageData->renderwidth = width; + building.HDImageData->renderheight = height; + } + } } // Try to load weap2.shp and tesla coil's lightning shapes @@ -3218,7 +3231,7 @@ void BuildingTypeClass::Display(int x, int y, WindowNumberType window, HousesTyp //CC_Draw_Shape((Image_t *)Get_HDImage_Data(), 0, x, y, window, SHAPE_CENTER | SHAPE_WIN_REL, NULL, NULL, DIR_N); x += WindowList[window][WINDOWX]; y += WindowList[window][WINDOWY]; - GL_RenderImage(hdimage, x, y, hdimage->width, hdimage->height); + GL_RenderImage(hdimage, x, y, hdimage->renderwidth, hdimage->renderheight); } else { void const* ptr = Get_Cameo_Data(); @@ -3376,8 +3389,23 @@ void BuildingTypeClass::Init(TheaterType theater) // jmarshall { char imageFileName[512]; - sprintf(imageFileName, "textures/%s.png", fullname); - ((BuildingTypeClass *)classptr)->HDImageData = Image_LoadImage(imageFileName); + + // Try load the theatre specific asset first + sprintf(imageFileName, "DATA/ART/TEXTURES/SRGB/RED_ALERT/TERRAIN/%s/%s.%s/%s.%s-0000.DDS", Theaters[theater].Name, classptr->Graphic_Name(), Theaters[theater].Suffix, classptr->Graphic_Name(), Theaters[theater].Suffix); + ((BuildingTypeClass*)classptr)->HDImageData = Image_LoadImage(imageFileName); + + if (!((BuildingTypeClass*)classptr)->HDImageData) { + sprintf(imageFileName, "DATA/ART/TEXTURES/SRGB/RED_ALERT/STRUCTURES/%s/%s-0000.TGA", classptr->Graphic_Name(), classptr->Graphic_Name()); + ((BuildingTypeClass*)classptr)->HDImageData = Image_LoadImage(imageFileName); + } + + if (classptr->ImageData != NULL && classptr->HDImageData != NULL) { + int width = Get_Build_Frame_Width(classptr->ImageData); + int height = Get_Build_Frame_Height(classptr->ImageData); + + classptr->HDImageData->renderwidth = width; + classptr->HDImageData->renderheight = height; + } } // jmarshall end /* diff --git a/code/REDALERT/CONQUER.CPP b/code/REDALERT/CONQUER.CPP index 3f5f763..f44bde6 100644 --- a/code/REDALERT/CONQUER.CPP +++ b/code/REDALERT/CONQUER.CPP @@ -3498,8 +3498,8 @@ void CC_Draw_Shape(Image_t* image, int shapenum, int x, int y, WindowNumberType if (image != NULL && shapenum != -1) { - int width = image->width; - int height = image->height; + int width = image->renderwidth; + int height = image->renderheight; #ifdef NEVER /* diff --git a/code/REDALERT/Image.h b/code/REDALERT/Image.h index 8ca0c14..7df1f95 100644 --- a/code/REDALERT/Image.h +++ b/code/REDALERT/Image.h @@ -9,6 +9,8 @@ struct Image_t { unsigned int image; int width; int height; + int renderwidth; + int renderheight; unsigned char* buffer; }; diff --git a/code/REDALERT/WINSTUB.CPP b/code/REDALERT/WINSTUB.CPP index ce14c0a..7c0c618 100644 --- a/code/REDALERT/WINSTUB.CPP +++ b/code/REDALERT/WINSTUB.CPP @@ -45,6 +45,7 @@ #include "examples/imgui_impl_sdl.h" #include "function.h" #include "il/il.h" +#include "il/ilu.h" #include "image.h" #ifdef WINSOCK_IPX @@ -106,6 +107,10 @@ Image_t* Image_LoadImage(const char* name) { return NULL; } + if (strstr(name, ".TGA")) { + iluFlipImage(); + } + ILuint Width, Height, Bpp; Width = ilGetInteger(IL_IMAGE_WIDTH); Height = ilGetInteger(IL_IMAGE_HEIGHT); @@ -135,8 +140,8 @@ Image_t* Image_LoadImage(const char* name) { Image_t* image = new Image_t(); strcpy(image->name, name); image->image = texture; - image->width = Width; - image->height = Height; + image->renderwidth = image->width = Width; + image->renderheight = image->height = Height; image->buffer = new unsigned char[Width * Height * Bpp]; memcpy(image->buffer, Data, Width * Height * Bpp); loaded_images.push_back(image); @@ -199,8 +204,8 @@ Image_t* Image_CreateImageFrom8Bit(const char* name, int Width, int Height, unsi strcpy(image->name, name); image->image = texture; - image->width = Width; - image->height = Height; + image->renderwidth = image->width = Width; + image->renderheight = image->height = Height; loaded_images.push_back(image); return image; } diff --git a/code/external/devil/ILU.lib b/code/external/devil/ILU.lib new file mode 100644 index 0000000000000000000000000000000000000000..5ea04cb70b1e618868e0dcae326fc41578989d45 GIT binary patch literal 12006 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 Date: Fri, 19 Jun 2020 08:06:12 -0700 Subject: [PATCH 02/10] Radar now works properly in OGL. --- code/REDALERT/RADAR.CPP | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/code/REDALERT/RADAR.CPP b/code/REDALERT/RADAR.CPP index 4c151e3..7626dd6 100644 --- a/code/REDALERT/RADAR.CPP +++ b/code/REDALERT/RADAR.CPP @@ -67,7 +67,7 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "function.h" - +#include "Image.h" //void const * RadarClass::CoverShape; RadarClass::RTacticalClass RadarClass::RadarButton; @@ -1073,7 +1073,7 @@ void RadarClass::Plot_Radar_Pixel(CELL cell) */ if (color == TBLACK) { if (ZoomFactor > 1) { - void const * ptr = NULL; + const TemplateTypeClass* ptr = NULL; int icon; /* @@ -1081,7 +1081,7 @@ void RadarClass::Plot_Radar_Pixel(CELL cell) ** specified cell. */ if (cellptr->TType != TEMPLATE_NONE && cellptr->TType != 255) { - ptr = TemplateTypeClass::As_Reference(cellptr->TType).Get_Image_Data(); + ptr = &TemplateTypeClass::As_Reference(cellptr->TType); icon = cellptr->TIcon; } @@ -1090,11 +1090,11 @@ void RadarClass::Plot_Radar_Pixel(CELL cell) ** template or an illegal one. Setup for a clear template. */ if (ptr == NULL) { - ptr = TemplateTypeClass::As_Reference(TEMPLATE_CLEAR1).Get_Image_Data(); + ptr = &TemplateTypeClass::As_Reference(TEMPLATE_CLEAR1); icon = cellptr->Clear_Icon(); } - IconsetClass const * iconset = (IconsetClass const *)ptr; + IconsetClass const * iconset = (IconsetClass const *)ptr->Get_Image_Data(); unsigned char const * icondata = iconset->Icon_Data(); @@ -1103,10 +1103,18 @@ void RadarClass::Plot_Radar_Pixel(CELL cell) */ icon &= 0x00FF; icon = *(iconset->Map_Data() + icon); - +// jmarshall unsigned char * data = (unsigned char *)icondata + icon*(24*24); - Buffer_To_Page(0, 0, 24, 24, data, _TileStage); - _TileStage.Scale(*LogicPage, 0, 0, x, y, 24, 24, ZoomFactor, ZoomFactor, TRUE); + Image_t* image; + { + char tmp[512]; + sprintf(tmp, "radar_%d_%d", ptr->Type, icon); + image = Image_CreateImageFrom8Bit(tmp, 24, 24, (unsigned char*)data); + } + GL_RenderImage(image, x, y, ZoomFactor, ZoomFactor); +// jmarshall end + //Buffer_To_Page(0, 0, 24, 24, data, _TileStage); + //_TileStage.Scale(*LogicPage, 0, 0, x, y, 24, 24, ZoomFactor, ZoomFactor, TRUE); } else { // LogicPage->Fill_Rect(x, y, x+ZoomFactor-1, y+ZoomFactor-1, cellptr->Cell_Color(false)); /*BG*/ LogicPage->Put_Pixel(x, y, cellptr->Cell_Color(false)); From 3f1e353296c202b96215d3d9e1e0885977f6064e Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Fri, 19 Jun 2020 08:57:02 -0700 Subject: [PATCH 03/10] Added hash comparisons to image load code. --- code/REDALERT/CONQUER.CPP | 2 +- code/REDALERT/Image.h | 3 ++- code/REDALERT/WINSTUB.CPP | 30 +++++++++++++++++++++++++++--- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/code/REDALERT/CONQUER.CPP b/code/REDALERT/CONQUER.CPP index f44bde6..1762cac 100644 --- a/code/REDALERT/CONQUER.CPP +++ b/code/REDALERT/CONQUER.CPP @@ -3758,7 +3758,7 @@ void CC_Draw_Shape(void const * shapefile, int shapenum, int x, int y, WindowNum { char tmp[512]; - sprintf(tmp, "s%d%d%d%d%d", shapefile, shapenum, rotation, fadingdata, flags); + sprintf(tmp, "s_%d_%d_%d_%d_%d", shapefile, shapenum, rotation, fadingdata, flags); if (flags & SHAPE_FADING) { shape_image = Image_CreateImageFrom8Bit(tmp, width, height, (unsigned char*)buffer, (unsigned char*)fadingdata); diff --git a/code/REDALERT/Image.h b/code/REDALERT/Image.h index 7df1f95..0e08fa7 100644 --- a/code/REDALERT/Image.h +++ b/code/REDALERT/Image.h @@ -6,11 +6,12 @@ struct Image_t { ~Image_t(); char name[512]; + int64_t namehash; unsigned int image; int width; int height; int renderwidth; - int renderheight; + int renderheight; unsigned char* buffer; }; diff --git a/code/REDALERT/WINSTUB.CPP b/code/REDALERT/WINSTUB.CPP index 7c0c618..1839060 100644 --- a/code/REDALERT/WINSTUB.CPP +++ b/code/REDALERT/WINSTUB.CPP @@ -91,10 +91,30 @@ void Show_OldFrameBuffer(bool show) { std::vector loaded_images; +/* +================ +return a hash value for the filename +================ +*/ +static int64_t generateHashValue(const char* fname, const int size) { + uint32_t hash = 0x811c9dc5; + uint32_t prime = 0x1000193; + + for (int i = 0; i < size; ++i) { + uint8_t value = fname[i]; + hash = hash ^ value; + hash *= prime; + } + + return hash; +} + Image_t* Image_LoadImage(const char* name) { + int64_t hash = generateHashValue(name, strlen(name)); + // Check to see if the image is already loaded. for (int i = 0; i < loaded_images.size(); i++) { - if (!strcmp(loaded_images[i]->name, name)) { + if (loaded_images[i]->namehash == hash) { return loaded_images[i]; } } @@ -140,6 +160,7 @@ Image_t* Image_LoadImage(const char* name) { Image_t* image = new Image_t(); strcpy(image->name, name); image->image = texture; + image->namehash = hash; image->renderwidth = image->width = Width; image->renderheight = image->height = Height; image->buffer = new unsigned char[Width * Height * Bpp]; @@ -152,12 +173,14 @@ Image_t* Image_LoadImage(const char* name) { } Image_t* Image_CreateImageFrom8Bit(const char* name, int Width, int Height, unsigned char *data, unsigned char* remap) { + int64_t hash = generateHashValue(name, strlen(name)); + // Check to see if the image is already loaded. for (int i = 0; i < loaded_images.size(); i++) { - if (!strcmp(loaded_images[i]->name, name)) { + if (loaded_images[i]->namehash == hash) { return loaded_images[i]; } - } + } unsigned char* ccpalete = (unsigned char*)CCPalette.Get_Data(); @@ -204,6 +227,7 @@ Image_t* Image_CreateImageFrom8Bit(const char* name, int Width, int Height, unsi strcpy(image->name, name); image->image = texture; + image->namehash = hash; image->renderwidth = image->width = Width; image->renderheight = image->height = Height; loaded_images.push_back(image); From 681c762dce86615211c0bb2eec538e19195cf456 Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Fri, 19 Jun 2020 15:26:41 -0700 Subject: [PATCH 04/10] Added house palette to remaster textures, AO is missing. --- code/REDALERT/BDATA.CPP | 2 +- code/REDALERT/CONQUER.CPP | 6 +- code/REDALERT/Image.h | 8 ++- code/REDALERT/NEWBLIT.CPP | 4 +- code/REDALERT/NEWBLIT.H | 2 +- code/REDALERT/OBJECT.CPP | 3 +- code/REDALERT/Shape.cpp | 6 +- code/REDALERT/TECHNO.CPP | 12 +++- code/REDALERT/VDATA.CPP | 3 +- code/REDALERT/WIN32LIB/MOUSEWW.CPP | 2 +- code/REDALERT/WINSTUB.CPP | 106 +++++++++++++++++++++++------ code/TIBERIANDAWN/GSCREEN.CPP | 3 +- 12 files changed, 117 insertions(+), 40 deletions(-) diff --git a/code/REDALERT/BDATA.CPP b/code/REDALERT/BDATA.CPP index e452ce0..56553f7 100644 --- a/code/REDALERT/BDATA.CPP +++ b/code/REDALERT/BDATA.CPP @@ -3143,7 +3143,7 @@ void BuildingTypeClass::One_Time(void) { char imageFileName[512]; sprintf(imageFileName, "DATA/ART/TEXTURES/SRGB/RED_ALERT/STRUCTURES/%s/%s-0000.TGA", building.Graphic_Name(), building.Graphic_Name()); - ((void const*&)building.HDImageData) = Image_LoadImage(imageFileName); + ((void const*&)building.HDImageData) = Image_LoadImage(imageFileName, true, true); if (building.ImageData != NULL && building.HDImageData != NULL) { int width = Get_Build_Frame_Width(building.ImageData); diff --git a/code/REDALERT/CONQUER.CPP b/code/REDALERT/CONQUER.CPP index 1762cac..2455d61 100644 --- a/code/REDALERT/CONQUER.CPP +++ b/code/REDALERT/CONQUER.CPP @@ -3486,9 +3486,9 @@ void CC_Draw_Shape(Image_t* image, int shapenum, int x, int y, WindowNumberType if ((flags & SHAPE_GHOST) && (!ghostdata)) { ghostdata = DisplayClass::SpecialGhost; } - if ((flags & SHAPE_FADING) && (!fadingdata)) { - fadingdata = DisplayClass::FadingShade; - } + //if ((flags & SHAPE_FADING) && (!fadingdata)) { + // fadingdata = DisplayClass::FadingShade; + //} static unsigned char* _xbuffer = 0; diff --git a/code/REDALERT/Image.h b/code/REDALERT/Image.h index 0e08fa7..3631a73 100644 --- a/code/REDALERT/Image.h +++ b/code/REDALERT/Image.h @@ -1,13 +1,16 @@ // Image.h // +#define MAX_IMAGE_FRAMES 256 +#define MAX_HOUSE_COLORS 8 struct Image_t { Image_t(); ~Image_t(); char name[512]; int64_t namehash; - unsigned int image; + unsigned int image[MAX_HOUSE_COLORS][MAX_IMAGE_FRAMES]; + unsigned int numAnimFrames; int width; int height; int renderwidth; @@ -17,6 +20,7 @@ struct Image_t { __forceinline Image_t::Image_t() { buffer = NULL; + numAnimFrames = 0; } __forceinline Image_t::~Image_t() { @@ -26,5 +30,5 @@ __forceinline Image_t::~Image_t() { } } -Image_t* Image_LoadImage(const char* name); +Image_t* Image_LoadImage(const char* name, bool loadAnims = false, bool loadHouseColor = false); Image_t* Image_CreateImageFrom8Bit(const char* name, int Width, int Height, unsigned char* data, unsigned char *remap = NULL); \ No newline at end of file diff --git a/code/REDALERT/NEWBLIT.CPP b/code/REDALERT/NEWBLIT.CPP index 0e92de6..693953c 100644 --- a/code/REDALERT/NEWBLIT.CPP +++ b/code/REDALERT/NEWBLIT.CPP @@ -18,10 +18,10 @@ void GL_ResetClipRect(void) { ImGui::GetForegroundDrawList()->PopClipRect(); } -void GL_RenderImage(Image_t* image, int x, int y, int width, int height) { +void GL_RenderImage(Image_t* image, int x, int y, int width, int height, int colorRemap) { ImVec2 mi(x, y); ImVec2 ma(x + width, y + height); - ImGui::GetForegroundDrawList()->AddImage((ImTextureID)image->image, mi, ma); + ImGui::GetForegroundDrawList()->AddImage((ImTextureID)image->image[colorRemap][0], mi, ma); } void GL_FillRect(int color, int x, int y, int width, int height) { diff --git a/code/REDALERT/NEWBLIT.H b/code/REDALERT/NEWBLIT.H index 84eba03..b3e9508 100644 --- a/code/REDALERT/NEWBLIT.H +++ b/code/REDALERT/NEWBLIT.H @@ -2,7 +2,7 @@ // struct Image_t; -void GL_RenderImage(Image_t* image, int x, int y, int width, int height); +void GL_RenderImage(Image_t* image, int x, int y, int width, int height, int colorRemap = 0); void GL_DrawText(int color, int x, int y, char* text); void GL_FillRect(int color, int x, int y, int width, int height); void GL_DrawLine(int color, int x, int y, int dx, int dy); diff --git a/code/REDALERT/OBJECT.CPP b/code/REDALERT/OBJECT.CPP index ad1b2e4..6b39275 100644 --- a/code/REDALERT/OBJECT.CPP +++ b/code/REDALERT/OBJECT.CPP @@ -2182,7 +2182,8 @@ ObjectTypeClass::ObjectTypeClass( Armor(ARMOR_NONE), MaxStrength(0), ImageData(0), - RadarIcon(0) + RadarIcon(0), + HDImageData(0) { /* ** Init the DimensionData rect. Not sure how this was ever working before without being allocated. It was just trashing diff --git a/code/REDALERT/Shape.cpp b/code/REDALERT/Shape.cpp index 9c2480e..7fa8e26 100644 --- a/code/REDALERT/Shape.cpp +++ b/code/REDALERT/Shape.cpp @@ -1546,8 +1546,10 @@ long Buffer_Frame_To_Page(int x, int y, int width, int height, struct Image_t * xstart = xstart + WindowList[Window][WINDOWX];// + LogicPage->Get_XPos(); ystart = ystart + WindowList[Window][WINDOWY];// + LogicPage->Get_YPos(); //GL_SetClipRect(WindowList[Window][WINDOWX], WindowList[Window][WINDOWY], WindowList[Window][WINDOWWIDTH], WindowList[Window][WINDOWWIDTH]); - GL_RenderImage(shape_image, xstart, ystart, width, height); - + if(renderHDTexture) + GL_RenderImage(shape_image, xstart, ystart, width, height, (int)fade_table); + else + GL_RenderImage(shape_image, xstart, ystart, width, height, 0); // Here we just use the function that will blit the entire frame // using the appropriate effects. diff --git a/code/REDALERT/TECHNO.CPP b/code/REDALERT/TECHNO.CPP index 71ff47a..7f9ea58 100644 --- a/code/REDALERT/TECHNO.CPP +++ b/code/REDALERT/TECHNO.CPP @@ -4451,11 +4451,17 @@ VisualType TechnoClass::Visual_Character(bool raw) const } void TechnoClass::Techno_Draw_Object_HD(struct Image_t* image, int shapenum, int x, int y, WindowNumberType window, DirType rotation, int scale) const { - void const* remap = Remap_Table(); + //void const* remap = Remap_Table(); void const* shadow = Map.UnitShadow; - y -= Lepton_To_Pixel(Height); - CC_Draw_Shape(image, shapenum, x, y, window, SHAPE_CENTER | SHAPE_WIN_REL | SHAPE_FADING | SHAPE_GHOST, remap, shadow, rotation); + PlayerColorType remapColor = PCOLOR_GOLD; + if (Techno_Type_Class()->IsRemappable) { + remapColor = House->RemapColor; + } + + y -= Lepton_To_Pixel(Height); + + CC_Draw_Shape(image, shapenum, x, y, window, SHAPE_CENTER | SHAPE_WIN_REL | SHAPE_FADING | SHAPE_GHOST, (const void *)remapColor, shadow, rotation); } /*********************************************************************************************** diff --git a/code/REDALERT/VDATA.CPP b/code/REDALERT/VDATA.CPP index e5d04fa..1e111e5 100644 --- a/code/REDALERT/VDATA.CPP +++ b/code/REDALERT/VDATA.CPP @@ -49,7 +49,7 @@ #include "function.h" - +#include "Image.h" // Submarine static VesselTypeClass const VesselSubmarine( @@ -578,7 +578,6 @@ void VesselTypeClass::One_Time(void) */ _makepath(fullname, NULL, NULL, uclass.Graphic_Name(), ".SHP"); ((void const *&)uclass.ImageData) = MFCD::Retrieve(fullname); - ((int &)uclass.MaxSize) = 26; } } diff --git a/code/REDALERT/WIN32LIB/MOUSEWW.CPP b/code/REDALERT/WIN32LIB/MOUSEWW.CPP index e41649d..da56f02 100644 --- a/code/REDALERT/WIN32LIB/MOUSEWW.CPP +++ b/code/REDALERT/WIN32LIB/MOUSEWW.CPP @@ -252,7 +252,7 @@ void WWMouseClass::RenderMouse(void) { ImGui::SetMouseCursor(ImGuiMouseCursor_None); - ImGui::GetForegroundDrawList()->AddImage((ImTextureID)current_cursor->image, mi, ma); + ImGui::GetForegroundDrawList()->AddImage((ImTextureID)current_cursor->image[0][0], mi, ma); } void WWMouseClass::Low_Show_Mouse(int x, int y) diff --git a/code/REDALERT/WINSTUB.CPP b/code/REDALERT/WINSTUB.CPP index 1839060..6948342 100644 --- a/code/REDALERT/WINSTUB.CPP +++ b/code/REDALERT/WINSTUB.CPP @@ -109,26 +109,19 @@ static int64_t generateHashValue(const char* fname, const int size) { return hash; } -Image_t* Image_LoadImage(const char* name) { - int64_t hash = generateHashValue(name, strlen(name)); - - // Check to see if the image is already loaded. - for (int i = 0; i < loaded_images.size(); i++) { - if (loaded_images[i]->namehash == hash) { - return loaded_images[i]; - } - } - +static bool Image_loadHDImage(Image_t *image, const char* name, int houseid, int animid, int64_t hash, unsigned char remap[3]) { ILuint ImageName; ilGenImages(1, &ImageName); ilBindImage(ImageName); if (!ilLoadImage(name)) { ilDeleteImages(1, &ImageName); - return NULL; + return false; } + bool swapBGR = false; if (strstr(name, ".TGA")) { iluFlipImage(); + swapBGR = true; } ILuint Width, Height, Bpp; @@ -137,12 +130,31 @@ Image_t* Image_LoadImage(const char* name) { Bpp = ilGetInteger(IL_IMAGE_BPP); ILubyte* Data = ilGetData(); + if (Bpp == 4 && (remap[0] + remap[1] + remap[2]) > 0) { + for (int i = 0; i < Width * Height; i++) { + //float g = (Data[(i * 4) + 1] / 255.0f); + if (Data[(i * 4) + 1] > Data[(i * 4) + 0] && Data[(i * 4) + 1] > Data[(i * 4) + 2]) { + + for (int c = 0; c < 3; c++) { + //float f = (remap[2 - c]) / 255.0f; + //f = f * min((g * 2.0f), 1.0f); + Data[(i * 4) + c] = remap[2 - c] << 2; //((unsigned char)(f * 255.0f)) << 2; //((unsigned char)f) << 2; + } + } + } + } + GLuint texture; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); if (Bpp == 4) { - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Width, Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, Data); + if (swapBGR) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Width, Height, 0, GL_BGRA, GL_UNSIGNED_BYTE, Data); + } + else { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Width, Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, Data); + } } else if (Bpp == 3) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Width, Height, 0, GL_RGB, GL_UNSIGNED_BYTE, Data); @@ -157,28 +169,79 @@ Image_t* Image_LoadImage(const char* name) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - Image_t* image = new Image_t(); + strcpy(image->name, name); - image->image = texture; + image->image[houseid][animid] = texture; image->namehash = hash; image->renderwidth = image->width = Width; image->renderheight = image->height = Height; image->buffer = new unsigned char[Width * Height * Bpp]; - memcpy(image->buffer, Data, Width * Height * Bpp); - loaded_images.push_back(image); + if (houseid == 0 && animid == 0) { + memcpy(image->buffer, Data, Width * Height * Bpp); + } ilDeleteImages(1, &ImageName); + return true; +} + +Image_t* Image_LoadImage(const char* name, bool loadAnims, bool loadHouseColor) { + int64_t hash = generateHashValue(name, strlen(name)); + + int image_table_size = loaded_images.size(); + if (image_table_size > 0) + { + Image_t** image_table = &loaded_images[0]; + + // Check to see if the image is already loaded. + for (int i = 0; i < image_table_size; i++) { + if (image_table[i]->namehash == hash) { + return image_table[i]; + } + } + } + Image_t* image = new Image_t(); + + if (!loadAnims) { + unsigned char remap[3] = { 0, 0, 0 }; + if (!Image_loadHDImage(image, name, 0, 0, hash, remap)) { + delete image; + return NULL; + } + } + else { + byte* palette = (byte *)CCPalette.Get_Data(); + for (int i = 0; i < MAX_MPLAYER_COLORS; i++) { + unsigned char p = ColorRemaps[i].Color; + unsigned char remap[3]; + + remap[0] = palette[(p * 3) + 0]; + remap[1] = palette[(p * 3) + 1]; + remap[2] = palette[(p * 3) + 2]; + if (!Image_loadHDImage(image, name, i, 0, hash, remap)) { + delete image; + return NULL; + } + } + } + + loaded_images.push_back(image); return image; } Image_t* Image_CreateImageFrom8Bit(const char* name, int Width, int Height, unsigned char *data, unsigned char* remap) { int64_t hash = generateHashValue(name, strlen(name)); - // Check to see if the image is already loaded. - for (int i = 0; i < loaded_images.size(); i++) { - if (loaded_images[i]->namehash == hash) { - return loaded_images[i]; + int image_table_size = loaded_images.size(); + if (image_table_size > 0) + { + Image_t** image_table = &loaded_images[0]; + + // Check to see if the image is already loaded. + for (int i = 0; i < image_table_size; i++) { + if (image_table[i]->namehash == hash) { + return image_table[i]; + } } } @@ -226,7 +289,8 @@ Image_t* Image_CreateImageFrom8Bit(const char* name, int Width, int Height, unsi strcpy(image->name, name); - image->image = texture; + image->image[0][0] = texture; + image->numAnimFrames = -1; image->namehash = hash; image->renderwidth = image->width = Width; image->renderheight = image->height = Height; diff --git a/code/TIBERIANDAWN/GSCREEN.CPP b/code/TIBERIANDAWN/GSCREEN.CPP index 95c47eb..e241d0c 100644 --- a/code/TIBERIANDAWN/GSCREEN.CPP +++ b/code/TIBERIANDAWN/GSCREEN.CPP @@ -492,6 +492,7 @@ extern bool CanVblankSync; *=============================================================================================*/ void GScreenClass::Blit_Display(void) { +#if 0 // jmarshall - no longer used and saves on performance to disable it(3.6%). if (SeenBuff.Get_Width()!=320){ #if (0) if (HidPage.Get_IsDirectDraw() && (Options.GameSpeed >1 || Options.ScrollRate==6 && CanVblankSync) ){ @@ -520,7 +521,7 @@ void GScreenClass::Blit_Display(void) // ST - 1/2/2019 5:26PM //ModeX_Blit (&HiddenPage); } - +#endif } From 8a94ae2a460616236f108a1f2ea093e5ba355513 Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Fri, 19 Jun 2020 17:32:09 -0700 Subject: [PATCH 05/10] Some progress on the palette remapping bits. --- code/REDALERT/WINSTUB.CPP | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/code/REDALERT/WINSTUB.CPP b/code/REDALERT/WINSTUB.CPP index 6948342..6d50246 100644 --- a/code/REDALERT/WINSTUB.CPP +++ b/code/REDALERT/WINSTUB.CPP @@ -109,6 +109,27 @@ static int64_t generateHashValue(const char* fname, const int size) { return hash; } +static void OffsetRemap(unsigned char remap[3]) { + int r = remap[0] << 2; + int g = remap[1] << 2; + int b = remap[2] << 2; + + // normalize by color instead of saturating to white + //if ((r | g | b) > 255) { + // int max; + // + // max = r > g ? r : g; + // max = max > b ? max : b; + // r = r * 255 / max; + // g = g * 255 / max; + // b = b * 255 / max; + //} + + remap[0] = r; + remap[1] = g; + remap[2] = b; +} + static bool Image_loadHDImage(Image_t *image, const char* name, int houseid, int animid, int64_t hash, unsigned char remap[3]) { ILuint ImageName; ilGenImages(1, &ImageName); @@ -131,14 +152,15 @@ static bool Image_loadHDImage(Image_t *image, const char* name, int houseid, int ILubyte* Data = ilGetData(); if (Bpp == 4 && (remap[0] + remap[1] + remap[2]) > 0) { - for (int i = 0; i < Width * Height; i++) { - //float g = (Data[(i * 4) + 1] / 255.0f); - if (Data[(i * 4) + 1] > Data[(i * 4) + 0] && Data[(i * 4) + 1] > Data[(i * 4) + 2]) { + OffsetRemap(remap); + for (int i = 0; i < Width * Height; i++) { + if (Data[(i * 4) + 1] > Data[(i * 4) + 0] && Data[(i * 4) + 1] > Data[(i * 4) + 2]) { + float g = (Data[(i * 4) + 1] / 255.0f); for (int c = 0; c < 3; c++) { - //float f = (remap[2 - c]) / 255.0f; - //f = f * min((g * 2.0f), 1.0f); - Data[(i * 4) + c] = remap[2 - c] << 2; //((unsigned char)(f * 255.0f)) << 2; //((unsigned char)f) << 2; + float f = ((remap[2 - c]) / 255.0f) * min(g + 0.25, 1); + f = f * 255.0f; + Data[(i * 4) + c] = ((unsigned int) f); //((unsigned char)(f * 255.0f)) << 2; //((unsigned char)f) << 2; } } } From 28e37d2d6eb21c4eeae4412776cbb0f93f17c0b2 Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Fri, 19 Jun 2020 17:54:59 -0700 Subject: [PATCH 06/10] Removed bad add in remaster palette rasterization. --- code/REDALERT/WINSTUB.CPP | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/REDALERT/WINSTUB.CPP b/code/REDALERT/WINSTUB.CPP index 6d50246..e6dd6ef 100644 --- a/code/REDALERT/WINSTUB.CPP +++ b/code/REDALERT/WINSTUB.CPP @@ -158,7 +158,7 @@ static bool Image_loadHDImage(Image_t *image, const char* name, int houseid, int if (Data[(i * 4) + 1] > Data[(i * 4) + 0] && Data[(i * 4) + 1] > Data[(i * 4) + 2]) { float g = (Data[(i * 4) + 1] / 255.0f); for (int c = 0; c < 3; c++) { - float f = ((remap[2 - c]) / 255.0f) * min(g + 0.25, 1); + float f = ((remap[2 - c]) / 255.0f) * min(g, 1); f = f * 255.0f; Data[(i * 4) + c] = ((unsigned int) f); //((unsigned char)(f * 255.0f)) << 2; //((unsigned char)f) << 2; } From b8a912f3a6643b50bedb4a0308096ed22116e450 Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Fri, 19 Jun 2020 19:37:06 -0700 Subject: [PATCH 07/10] Remaster palettes now work correctly. --- code/CMakeLists.txt | 2 +- code/REDALERT/HOUSECOLOR.H | 58 ++ code/REDALERT/IMGUTIL.H | 206 ++++ code/REDALERT/WINSTUB.CPP | 42 +- code/REDALERT/vectormath.h | 2020 ++++++++++++++++++++++++++++++++++++ 5 files changed, 2292 insertions(+), 36 deletions(-) create mode 100644 code/REDALERT/HOUSECOLOR.H create mode 100644 code/REDALERT/IMGUTIL.H create mode 100644 code/REDALERT/vectormath.h diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 525bad0..2a6e5a7 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -1,4 +1,4 @@ -# CMakeList.txt : CMake project for Red Alert +# CMakeList.txt : CMake project for Red Alert cmake_minimum_required (VERSION 3.8) project(RedAlert ASM CXX C) diff --git a/code/REDALERT/HOUSECOLOR.H b/code/REDALERT/HOUSECOLOR.H new file mode 100644 index 0000000..fb81f31 --- /dev/null +++ b/code/REDALERT/HOUSECOLOR.H @@ -0,0 +1,58 @@ +// HOUSECOLOR.H +// + +#include "vectormath.h" + +struct HouseColorHD { + HouseColorHD(float3 lowerbounds, + float3 upperbounds, + float fudge, + float3 hsvshift, + float3 inputlevels, + float2 outputlevels, + float3 overallinputlevels, + float2 overalloutputlevels, + float3 radarmapcolor) + { + this->LowerBounds = lowerbounds; + this->UpperBounds = upperbounds; + this->Fudge = fudge; + this->HSVShift = hsvshift; + this->InputLevels = inputlevels; + this->OutputLevels = outputlevels; + this->OverallInputLevels = overallinputlevels; + this->OverallOutputLevels = overalloutputlevels; + this->RadarMapColor = radarmapcolor; + } + + float3 LowerBounds; + float3 UpperBounds; + float Fudge; + float3 HSVShift; + float3 InputLevels; + float2 OutputLevels; + float3 OverallInputLevels; + float2 OverallOutputLevels; + float3 RadarMapColor; +}; + +// This data has been sourced from the TD Original Skirmish Color mod by Pokey +// +static HouseColorHD *houseColors[8] = { + // Spain + new HouseColorHD(float3(0.26f, 1.0f, 1.0f), float3(0.0f, 1.0f, 0.22f), 0, float3(0.777f,-0.196, -0.0f), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.737255, 0.627451, 0.239216) ), + // Greece + new HouseColorHD(float3(0.264706f, 1.0f, 0.0f), float3(0.0f, 1.0f, 0.22f), 0, float3(0.310000f,-0.211000, 0.005000), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.0980f, 0.3922, 0.9216)), + // USSR + new HouseColorHD(float3(0.264706f, 1.0f, 0.0f), float3(0.0f, 1.0f, 0.22f), 0, float3(0.689000f,0.382000, 0.000000), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.756863f, 0.0, 0.015686)), + // England + new HouseColorHD(float3(0.26, 1.0f, 0.0f), float3(0.0f, 1.0f, 0.22f), 0, float3(0.911f,-0.381, 0.000000), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.184314, 0.67451, 0.141176)), + // Ukraine + new HouseColorHD(float3(0.26, 1.0f, 0.0f), float3(0.0f, 1.0f, 0.22f), 0, float3(0.726,0.119, 0.000000), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.772549, 0.380392, 0.07451)), + // Germany + new HouseColorHD(float3(0.26, 1.0f, 0.0f), float3(0.0f, 1.0f, 0.22f), 0, float3(0.713,-0.621, 0.000000), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.6421568, 0.4784698, 0.4784698)), + // France + new HouseColorHD(float3(0.26, 1.0f, 0.0f), float3(0.0f, 1.0f, 0.22f), 0, float3(0.119,-0.233, 0.000000), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.1151961, 0.6911765, 0.4031861)), + // Turkey + new HouseColorHD(float3(0.26, 1.0f, 0.0f), float3(0.0f, 1.0f, 0.22f), 0, float3(0.685,-0.151, 0.000000), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.5784314, 0.1155861, 0.05387352)), +}; \ No newline at end of file diff --git a/code/REDALERT/IMGUTIL.H b/code/REDALERT/IMGUTIL.H new file mode 100644 index 0000000..98f35ea --- /dev/null +++ b/code/REDALERT/IMGUTIL.H @@ -0,0 +1,206 @@ +// IMGUTIL.H +// + +#ifdef _DEBUG +#pragma optimize( "", on ) +#endif + +// SRGB -> Linear color space conversion. +inline float ByteToLinear(byte p) { + float v; + v = ((float)p) / 255.0f; + v = (double)v >= 0.0404499992728233 ? (float)pow((200.0 * (double)v + 11.0) / 211.0, 2.40000009536743) : (float)((double)v * 25.0 / 323.0); + return v; +} + +inline float ToSRGB(float v) +{ + return (double)v >= 0.0031308 ? (float)((pow((double)v, 0.416666656732559) * 211.0 - 11.0) / 200.0) : (float)((double)v * 323.0 / 25.0); +} + +inline float PixelToHue(float R, float G, float B) { + if (R == G && G == B) + return 0; // 0 makes as good an UNDEFINED value as any + + float r = (float)R / 255.0f; + float g = (float)G / 255.0f; + float b = (float)B / 255.0f; + + float max, min; + float delta; + float hue = 0.0f; + + max = r; min = r; + + if (g > max) max = g; + if (b > max) max = b; + + if (g < min) min = g; + if (b < min) min = b; + + delta = max - min; + + if (r == max) { + hue = (g - b) / delta; + } + else if (g == max) { + hue = 2 + (b - r) / delta; + } + else if (b == max) { + hue = 4 + (r - g) / delta; + } + hue *= 60; + + if (hue < 0.0f) { + hue += 360.0f; + } + return hue; +} + +struct Turple { + Turple() { + memset(this, 0, sizeof(Turple)); + } + Turple(float Item1, float Item2, float Item3) { + this->Item1 = Item1; + this->Item2 = Item2; + this->Item3 = Item3; + this->Item4 = -1; + } + Turple(float Item1, float Item2, float Item3, float Item4) { + this->Item1 = Item1; + this->Item2 = Item2; + this->Item3 = Item3; + this->Item4 = Item4; + } + float Item1; + float Item2; + float Item3; + float Item4; +}; + + +float step(float a, float x) +{ + return x >= a; +} + +inline float4 lerp(float4 a, float4 b, float w) +{ + float4 l; + l.x = a.x + w * (b.x - a.x); + l.y = a.y + w * (b.y - a.y); + l.z = a.z + w * (b.z - a.z); + l.w = a.w + w * (b.w - a.w); + return l; +} + +inline float3 lerp(float3 a, float3 b, float w) +{ + float3 l; + l.x = a.x + w * (b.x - a.x); + l.y = a.y + w * (b.y - a.y); + l.z = a.z + w * (b.z - a.z); + return l; +} + +inline float3 frac(float3 v) +{ + float3 f; + f.x = v.x - floor(v.x); + f.y = v.y - floor(v.y); + f.z = v.z - floor(v.z); + return f; +} + +inline float frac(float v) +{ + return v - floor(v); +} + +inline float lerp(float a, float b, float w) +{ + return a + w * (b - a); +} + + +inline float3 clamp(float3 x, float a, float b) +{ + float3 r; + r.x = max(a, min(b, x.x)); + r.y = max(a, min(b, x.y)); + r.z = max(a, min(b, x.z)); + return r; +} + +inline float saturate(float x) +{ + return max(0, min(1, x)); +} + +inline void RenderTeamPalette(byte* pixel, HouseColorHD &teamColor) { + float linear1 = ByteToLinear(pixel[2]); + float linear2 = ByteToLinear(pixel[1]); + float linear3 = ByteToLinear(pixel[0]); + float num1 = linear1; + float num2 = linear2; + float num3 = (float)linear3; + Turple valueTuple1 = Turple(0.0f, -0.3333333, 0.6666667, -1 ); + Turple valueTuple2; + + if ((double)num2 >= (double)num3) + valueTuple2 = Turple( num2, num3, valueTuple1.Item1, valueTuple1.Item2 ); + else + valueTuple2 = Turple( num3, num2, valueTuple1.Item4, valueTuple1.Item3 ); + + Turple valueTuple3; + if ((double)num1 >= (double)valueTuple2.Item1) + valueTuple3 = Turple(num1, valueTuple2.Item2, valueTuple2.Item3, valueTuple2.Item1); + else + valueTuple3 = Turple(valueTuple2.Item1, valueTuple2.Item2, valueTuple2.Item4, num1); + double num4 = (double)valueTuple3.Item1 - (double)min(valueTuple3.Item4, valueTuple3.Item2); + float num5 = 1E-10f; + float num6 = (float)num4; + float num7 = num5; + float num8 = abs(valueTuple3.Item3 + (float)(((double)valueTuple3.Item4 - (double)valueTuple3.Item2) / (6.0 * (double)num6 + (double)num7))); + float num9 = num6 / (valueTuple3.Item1 + num7); + double num10 = (double)valueTuple3.Item1; + float num11 = num8; + float num12 = num9; + float num13 = (float)num10; + float num14 = PixelToHue(teamColor.LowerBounds.x, teamColor.LowerBounds.y, teamColor.LowerBounds.z) / 360; + float num15 = PixelToHue(teamColor.UpperBounds.x, teamColor.UpperBounds.y, teamColor.UpperBounds.z) / 360; + + if ((double)num11 >= (double)num14 && (double)num15 >= (double)num11) + { + float num16 = (float)((double)num11 / ((double)num15 - (double)num14) * ((double)num15 + (double)teamColor.Fudge - ((double)num14 - (double)teamColor.Fudge))) + teamColor.HSVShift.x; + float t = num12 + teamColor.HSVShift.y; + float num17 = num13 + teamColor.HSVShift.z; + Turple valueTuple4 = Turple(1.0f, 0.6666667, 0.3333333, 3); + Turple valueTuple5 = Turple(abs(frac(num16 + valueTuple4.Item1) * 6 - valueTuple4.Item4), abs(frac(num16 + valueTuple4.Item2) * 6 - valueTuple4.Item4), abs(frac(num16 + valueTuple4.Item3) * 6 - valueTuple4.Item4)); + float num18 = num17 * lerp(valueTuple4.Item1, saturate(valueTuple5.Item1 - valueTuple4.Item1), t); + float num19 = num17 * lerp(valueTuple4.Item1, saturate(valueTuple5.Item2 - valueTuple4.Item1), t); + float num20 = num17 * lerp(valueTuple4.Item1, saturate(valueTuple5.Item3 - valueTuple4.Item1), t); + Turple valueTuple6 = Turple(min(1, max(0.0f, num18 - teamColor.InputLevels.x) / (teamColor.InputLevels.z - teamColor.InputLevels.x)), min(1, max(0.0f, num19 - teamColor.InputLevels.x) / (teamColor.InputLevels.z - teamColor.InputLevels.x)), min(1, max(0.0f, num20 - teamColor.InputLevels.x) / (teamColor.InputLevels.z - teamColor.InputLevels.x))); + valueTuple6.Item1 = (float)pow((double)valueTuple6.Item1, (double)teamColor.InputLevels.y); + valueTuple6.Item2 = (float)pow((double)valueTuple6.Item2, (double)teamColor.InputLevels.y); + valueTuple6.Item3 = (float)pow((double)valueTuple6.Item3, (double)teamColor.InputLevels.y); + num1 = lerp(teamColor.OutputLevels.x, teamColor.OutputLevels.y, valueTuple6.Item1); + num2 = lerp(teamColor.OutputLevels.x, teamColor.OutputLevels.y, valueTuple6.Item2); + num3 = lerp(teamColor.OutputLevels.x, teamColor.OutputLevels.y, valueTuple6.Item3); + } + + Turple valueTuple7 = Turple(min(1, max(0.0f, num1 - teamColor.OverallInputLevels.x) / (teamColor.OverallInputLevels.z - teamColor.OverallInputLevels.x)), min(1, max(0.0f, num2 - teamColor.OverallInputLevels.x) / (teamColor.OverallInputLevels.z - teamColor.OverallInputLevels.x)), min(1, max(0.0f, num3 - teamColor.OverallInputLevels.x) / (teamColor.OverallInputLevels.z - teamColor.OverallInputLevels.x))); + valueTuple7.Item1 = (float)pow((double)valueTuple7.Item1, (double)teamColor.OverallInputLevels.y); + valueTuple7.Item2 = (float)pow((double)valueTuple7.Item2, (double)teamColor.OverallInputLevels.y); + valueTuple7.Item3 = (float)pow((double)valueTuple7.Item3, (double)teamColor.OverallInputLevels.y); + float v1 = lerp(teamColor.OverallOutputLevels.x, teamColor.OverallOutputLevels.y, valueTuple7.Item1); + float v2 = lerp(teamColor.OverallOutputLevels.x, teamColor.OverallOutputLevels.y, valueTuple7.Item2); + float v3 = lerp(teamColor.OverallOutputLevels.x, teamColor.OverallOutputLevels.y, valueTuple7.Item3); + pixel[2] = (byte)((double)ToSRGB(v1) * (double)255.0f); + pixel[1] = (byte)((double)ToSRGB(v2) * (double)255.0f); + pixel[0] = (byte)((double)ToSRGB(v3) * (double)255.0f); +} +#ifdef _DEBUG +#pragma optimize( "", off ) +#endif \ No newline at end of file diff --git a/code/REDALERT/WINSTUB.CPP b/code/REDALERT/WINSTUB.CPP index e6dd6ef..049e45f 100644 --- a/code/REDALERT/WINSTUB.CPP +++ b/code/REDALERT/WINSTUB.CPP @@ -63,6 +63,8 @@ #include #include +#include "HOUSECOLOR.H" +#include "IMGUTIL.H" GLuint backbuffer_texture = -1; //byte* backbuffer_data; @@ -109,28 +111,7 @@ static int64_t generateHashValue(const char* fname, const int size) { return hash; } -static void OffsetRemap(unsigned char remap[3]) { - int r = remap[0] << 2; - int g = remap[1] << 2; - int b = remap[2] << 2; - - // normalize by color instead of saturating to white - //if ((r | g | b) > 255) { - // int max; - // - // max = r > g ? r : g; - // max = max > b ? max : b; - // r = r * 255 / max; - // g = g * 255 / max; - // b = b * 255 / max; - //} - - remap[0] = r; - remap[1] = g; - remap[2] = b; -} - -static bool Image_loadHDImage(Image_t *image, const char* name, int houseid, int animid, int64_t hash, unsigned char remap[3]) { +static bool Image_loadHDImage(Image_t *image, const char* name, int houseid, int animid, int64_t hash, int houseId) { ILuint ImageName; ilGenImages(1, &ImageName); ilBindImage(ImageName); @@ -151,18 +132,9 @@ static bool Image_loadHDImage(Image_t *image, const char* name, int houseid, int Bpp = ilGetInteger(IL_IMAGE_BPP); ILubyte* Data = ilGetData(); - if (Bpp == 4 && (remap[0] + remap[1] + remap[2]) > 0) { - OffsetRemap(remap); - + if (Bpp == 4 && houseId != -1) { for (int i = 0; i < Width * Height; i++) { - if (Data[(i * 4) + 1] > Data[(i * 4) + 0] && Data[(i * 4) + 1] > Data[(i * 4) + 2]) { - float g = (Data[(i * 4) + 1] / 255.0f); - for (int c = 0; c < 3; c++) { - float f = ((remap[2 - c]) / 255.0f) * min(g, 1); - f = f * 255.0f; - Data[(i * 4) + c] = ((unsigned int) f); //((unsigned char)(f * 255.0f)) << 2; //((unsigned char)f) << 2; - } - } + RenderTeamPalette(&Data[i * 4], *houseColors[houseId]); } } @@ -226,7 +198,7 @@ Image_t* Image_LoadImage(const char* name, bool loadAnims, bool loadHouseColor) if (!loadAnims) { unsigned char remap[3] = { 0, 0, 0 }; - if (!Image_loadHDImage(image, name, 0, 0, hash, remap)) { + if (!Image_loadHDImage(image, name, 0, 0, hash, -1)) { delete image; return NULL; } @@ -240,7 +212,7 @@ Image_t* Image_LoadImage(const char* name, bool loadAnims, bool loadHouseColor) remap[0] = palette[(p * 3) + 0]; remap[1] = palette[(p * 3) + 1]; remap[2] = palette[(p * 3) + 2]; - if (!Image_loadHDImage(image, name, i, 0, hash, remap)) { + if (!Image_loadHDImage(image, name, i, 0, hash, i)) { delete image; return NULL; } diff --git a/code/REDALERT/vectormath.h b/code/REDALERT/vectormath.h new file mode 100644 index 0000000..3c5c405 --- /dev/null +++ b/code/REDALERT/vectormath.h @@ -0,0 +1,2020 @@ +//-------------------------------------------------------------------------------------- +// Copyright 2013 Intel Corporation +// All Rights Reserved +// +// Permission is granted to use, copy, distribute and prepare derivative works of this +// software for any purpose and without fee, provided, that the above copyright notice +// and this statement appear in all copies. Intel makes no representations about the +// suitability of this software for any purpose. THIS SOFTWARE IS PROVIDED "AS IS." +// INTEL SPECIFICALLY DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, AND ALL LIABILITY, +// INCLUDING CONSEQUENTIAL AND OTHER INDIRECT DAMAGES, FOR THE USE OF THIS SOFTWARE, +// INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PROPRIETARY RIGHTS, AND INCLUDING THE +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Intel does not +// assume any responsibility for any errors which may appear in this software nor any +// responsibility to update it. +//-------------------------------------------------------------------------------------- + +#ifndef __CPUTMath_h__ +#define __CPUTMath_h__ +#include + +/* + * Constants + */ +static const float kEpsilon = 0.00001f; +static const float kPi = 3.14159265358979323846f; +static const float k2Pi = kPi*2.0f; +static const float kPiDiv2 = kPi/2.0f; +static const float kInvPi = 1.0f/kPi; +static const float kDegToRad = (kPi/180.0f); +static const float kRadToDeg = (180.0f/kPi); +static inline float DegToRad( float fDeg ) { return fDeg * kDegToRad; } +static inline float RadToDeg( float fRad ) { return fRad * kRadToDeg; } + +template +static inline void Swap(T &a, T &b) +{ + T t = b; + b = a; + a = t; +} + +static inline double InvSqrt(const double x) +{ + return 1.0 / sqrt(x); +} + +struct float2; +struct float3; +struct float4; + +/**************************************\ +float2 +\**************************************/ +struct float2 +{ + union + { + struct + { + float x; + float y; + }; + float f[2]; + }; + + /***************************************\ + | Constructors | + \***************************************/ + float2() {} + explicit float2(float f) : x(f), y(f) { } + explicit float2(float _x, float _y) : x(_x), y(_y) { } + explicit float2(float* f) : x(f[0]), y(f[1]) { } + float2(const float2 &v) : x(v.x), y(v.y) { } + const float2 &operator=(const float2 &v) + { + x = v.x; + y = v.y; + return *this; + } + + /***************************************\ + | Basic math operations | + \***************************************/ + float2 operator+(const float2 &r) const + { + return float2(x+r.x, y+r.y); + } + const float2 &operator+=(const float2 &r) + { + x += r.x; + y += r.y; + return *this; + } + float2 operator-(const float2 &r) const + { + return float2(x-r.x, y-r.y); + } + const float2 &operator-=(const float2 &r) + { + x -= r.x; + y -= r.y; + return *this; + } + /***************************************\ + | Basic math operations with scalars | + \***************************************/ + float2 operator+(float f) const + { + return float2(x+f, y+f); + } + const float2 &operator+=(float f) + { + x += f; + y += f; + return *this; + } + float2 operator-(float f) const + { + return float2(x-f, y-f); + } + const float2 &operator-=(float f) + { + x -= f; + y -= f; + return *this; + } + float2 operator*(float f) const + { + return float2(x*f, y*f); + } + const float2 &operator*=(float f) + { + x *= f; + y *= f; + return *this; + } + float2 operator/(float f) const + { + return float2(x/f, y/f); + } + const float2 &operator/=(float f) + { + x /= f; + y /= f; + return *this; + } + + /***************************************\ + | Other math | + \***************************************/ + // Equality + bool operator==(const float2 &r) const + { + return (x==r.x && y == r.y); + } + bool operator!=(const float2 &r) const + { + return !(*this == r); + } + + // Hadd + float hadd(void) const + { + return x + y; + } + + // Length + float lengthSq(void) const + { + return x*x + y*y; + } + float length(void) const + { + return sqrtf(lengthSq()); + } + void normalize(void) + { + *this /= length(); + } +}; + +inline float dot2(const float2 &l, const float2 &r) +{ + return l.x*r.x + l.y*r.y; +} + +inline float2 normalize(const float2 &v) +{ + float length = v.length(); + return v / length; +} + + + + + + + + +/**************************************\ +float3 +\**************************************/ +struct float3 +{ + union + { + struct + { + float x; + float y; + float z; + }; + float f[3]; + }; + + static const float3 Left; + static const float3 Right; + static const float3 Up; + static const float3 Down; + static const float3 Forward; + static const float3 Backward; + static const float3 ZeroF; + + /***************************************\ + | Constructors | + \***************************************/ + float3() {} + explicit float3(float f) : x(f), y(f), z(f) { } + explicit float3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) { } + explicit float3(float* f) : x(f[0]), y(f[1]), z(f[2]) { } + float3(const float3 &v) : x(v.x), y(v.y), z(v.z) { } + float3(const float4 &v); + const float3 &operator=(const float3 &v) + { + x = v.x; + y = v.y; + z = v.z; + return *this; + } + + /***************************************\ + | Basic math operations | + \***************************************/ + float3 operator+(const float3 &r) const + { + return float3(x+r.x, y+r.y, z+r.z); + } + const float3 &operator+=(const float3 &r) + { + x += r.x; + y += r.y; + z += r.z; + return *this; + } + float3 operator-(const float3 &r) const + { + return float3(x-r.x, y-r.y, z-r.z); + } + float operator[](const int index) const { + return (&x)[index]; + } + + + float operator*(const float3 &a) const + { + return x * a.x + y * a.y + z * a.z; + } + + float Dot(const float3 &a) const + { + return x * a.x + y * a.y + z * a.z; + } + + static float Dot(const float3 &a, const float3 &b) + { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + + + float &operator[](const int index) { + return (&x)[index]; + } + const float3 &operator-=(const float3 &r) + { + x -= r.x; + y -= r.y; + z -= r.z; + return *this; + } + + float NormalizeFast() + { + float sqrLength, invLength; + + sqrLength = x * x + y * y + z * z; + invLength = InvSqrt(sqrLength); + x *= invLength; + y *= invLength; + z *= invLength; + return invLength * sqrLength; + } + + float3 operator*(float3 &r) + { + return float3(x*r.x, y*r.y, z*r.z); + } + + + float3 operator*(const float a) const { + return float3(x * a, y * a, z * a); + } + + friend float3 operator*(const float a, const float3 b) { + return float3(b.x * a, b.y * a, b.z * a); + } + + const float3 &operator*=(const float3 &r) + { + x *= r.x; + y *= r.y; + z *= r.z; + return *this; + } + float3 &operator*=(float3 &r) + { + x *= r.x; + y *= r.y; + z *= r.z; + return *this; + } + bool Compare(const float3 &a) const { + return ((x == a.x) && (y == a.y) && (z == a.z)); + } + + bool Compare(const float3 &a, const float epsilon) const { + if (fabs(x - a.x) > epsilon) { + return false; + } + + if (fabs(y - a.y) > epsilon) { + return false; + } + + if (fabs(z - a.z) > epsilon) { + return false; + } + + return true; + } + float3 operator/(const float3 &r) const + { + return float3(x/r.x, y/r.y, z/r.z); + } + const float3 &operator/=(const float3 &r) + { + x /= r.x; + y /= r.y; + z /= r.z; + return *this; + } + + /***************************************\ + | Basic math operations with scalars | + \***************************************/ + float3 operator+(float f) const + { + return float3(x+f, y+f, z+f); + } + const float3 &operator+=(float f) + { + x += f; + y += f; + z += f; + return *this; + } + float3 operator-(float f) const + { + return float3(x-f, y-f, z-f); + } + const float3 &operator-=(float f) + { + x -= f; + y -= f; + z -= f; + return *this; + } + + + const float3 &operator*=(float f) + { + x *= f; + y *= f; + z *= f; + return *this; + } + float3 operator/(float f) const + { + return float3(x/f, y/f, z/f); + } + const float3 &operator/=(float f) + { + x /= f; + y /= f; + z /= f; + return *this; + } + + void Zero() + { + x = 0; + y = 0; + z = 0; + } + + /***************************************\ + | Other math | + \***************************************/ + // Equality + bool operator==(const float3 &r) const + { + return x==r.x &&y == r.y &&z == r.z; + } + bool operator!=(const float3 &r) const + { + return !(*this == r); + } + + // Hadd + float hadd(void) const + { + return x + y + z; + } + + // Length + float lengthSq(void) const + { + return x*x + y*y + z*z; + } + float length(void) const + { + return sqrtf(lengthSq()); + } + + float3 normalize(void) + { + return (*this /= length()); + } + + float NormalizeSelf() { + float sqrLength, invLength; + + sqrLength = x * x + y * y + z * z; + invLength = InvSqrt(sqrLength); + x *= invLength; + y *= invLength; + z *= invLength; + return invLength * sqrLength; + } + float3 Cross(const float3 &a) const + { + return float3(y * a.z - z * a.y, z * a.x - x * a.z, x * a.y - y * a.x); + } + + float3 &Cross(const float3 &a, const float3 &b) + { + x = a.y * b.z - a.z * b.y; + y = a.z * b.x - a.x * b.z; + z = a.x * b.y - a.y * b.x; + + return *this; + } +}; + +inline float dot3(const float3 &l, const float3 &r) +{ + return l.x*r.x + l.y*r.y + l.z*r.z; +} +inline float3 cross3(const float3 &l, const float3 &r) +{ + return float3( l.y*r.z-l.z*r.y, + l.z*r.x-l.x*r.z, + l.x*r.y-l.y*r.x); +} +inline float3 normalize(const float3 &v) +{ + float length = v.length(); + return v / length; +} + +inline float3 abs3( const float3 &l ) +{ + float3 tmp; + tmp.x = fabs(l.x); + tmp.y = fabs(l.y); + tmp.z = fabs(l.z); + return tmp; +} + + +/**************************************\ +float4 +\**************************************/ +__declspec(align(16)) struct float4 +{ + union + { + struct + { + float x; + float y; + float z; + float w; + }; + float f[4]; + }; + + /***************************************\ + | Constructors | + \***************************************/ + float4() {} + explicit float4(float f) : x(f), y(f), z(f), w(f) { } + explicit float4(float _x, float _y, float _z, float _w) : x(_x), y(_y), z(_z), w(_w) { } + explicit float4(float* f) : x(f[0]), y(f[1]), z(f[2]), w(f[3]) { } + float4(const float4 &v) : x(v.x), y(v.y), z(v.z), w(v.w) { } + float4(const float3 &v, float _w) : x(v.x), y(v.y), z(v.z), w(_w) { } + const float4 &operator=(const float4 &v) + { + x = v.x; + y = v.y; + z = v.z; + w = v.w; + return *this; + } + + /***************************************\ + | Basic math operations | + \***************************************/ + float4 operator+(const float4 &r) const + { + return float4(x+r.x, y+r.y, z+r.z, w+r.w); + } + const float4 &operator+=(const float4 &r) + { + x += r.x; + y += r.y; + z += r.z; + w += r.w; + return *this; + } + float4 operator-(const float4 &r) const + { + return float4(x-r.x, y-r.y, z-r.z, w-r.w); + } + const float4 &operator-=(const float4 &r) + { + x -= r.x; + y -= r.y; + z -= r.z; + w -= r.w; + return *this; + } + float4 operator*(const float4 &r) const + { + return float4(x*r.x, y*r.y, z*r.z, w*r.w); + } + const float4 &operator*=(const float4 &r) + { + x *= r.x; + y *= r.y; + z *= r.z; + w *= r.w; + return *this; + } + float4 operator/(const float4 &r) const + { + return float4(x/r.x, y/r.y, z/r.z, w/r.w); + } + const float4 &operator/=(const float4 &r) + { + x /= r.x; + y /= r.y; + z /= r.z; + w /= r.w; + return *this; + } + + /***************************************\ + | Basic math operations with scalars | + \***************************************/ + float4 operator+(float f) const + { + return float4(x+f, y+f, z+f, w+f); + } + const float4 &operator+=(float f) + { + x += f; + y += f; + z += f; + return *this; + } + float4 operator-(float f) const + { + return float4(x-f, y-f, z-f, w-f); + } + const float4 &operator-=(float f) + { + x -= f; + y -= f; + z -= f; + w -= f; + return *this; + } + float4 operator*(float f) const + { + return float4(x*f, y*f, z*f, w*f); + } + const float4 &operator*=(float f) + { + x *= f; + y *= f; + z *= f; + w *= f; + return *this; + } + float4 operator/(float f) const + { + return float4(x/f, y/f, z/f, w/f); + } + const float4 &operator/=(float f) + { + x /= f; + y /= f; + z /= f; + w /= f; + return *this; + } + + /***************************************\ + | Other math | + \***************************************/ + // Equality + bool operator==(const float4 &r) const + { + return x==r.x && y == r.y && z == r.z && w == r.w; + } + bool operator!=(const float4 &r) const + { + return !(*this == r); + } + + // Hadd + float hadd(void) const + { + return x + y + z + w; + } + + // Length + float lengthSq(void) const + { + return x*x + y*y + z*z + w*w; + } + float length(void) const + { + return sqrtf(lengthSq()); + } + void normalize(void) + { + *this /= length(); + } +}; + +inline float dot4(const float4 &l, const float4 &r) +{ + return l.x*r.x + l.y*r.y + l.z*r.z + l.w*r.w; +} +inline float4 normalize(const float4 &v) +{ + float length = v.length(); + return v / length; +} + +inline float3::float3(const float4 &v) : x(v.x), y(v.y), z(v.z) { } + + +/**************************************\ +float3x3 +\**************************************/ +struct float4x4; +struct float3x3 +{ + struct + { + float3 r0; + float3 r1; + float3 r2; + }; + + /***************************************\ + | Constructors | + \***************************************/ + float3x3() {} + explicit float3x3(float f) : r0(f), r1(f), r2(f) { } + explicit float3x3(float* f) : r0(f+0), r1(f+3), r2(f+6) { } + float3x3(const float3 &_r0, const float3 &_r1, const float3 &_r2) : r0(_r0), r1(_r1), r2(_r2) { } + float3x3(float _m00, float _m01, float _m02, + float _m10, float _m11, float _m12, + float _m20, float _m21, float _m22) + : r0(_m00,_m01,_m02) + , r1(_m10,_m11,_m12) + , r2(_m20,_m21,_m22) + { + } + float3x3(const float4x4 &m); + const float3x3 &operator=(const float3x3 &m) + { + r0 = m.r0; + r1 = m.r1; + r2 = m.r2; + return *this; + } + + /***************************************\ + | Basic math operations | + \***************************************/ + + #define MTX3_INDEX(f,r,c) ((f)[(r*3)+c]) + inline float3x3 operator*(const float3x3 &r) const + { + float3x3 m(1,0,0,0,1,0,0,0,1); + + const float* left = (const float*)&this->r0; + const float* right = (const float*)&r.r0; + float* result = (float*)&m; + + int ii, jj, kk; + for(ii=0; ii<3; ++ii) /* row */ + { + for(jj=0; jj<3; ++jj) /* column */ + { + float sum = MTX3_INDEX(left,ii,0)*MTX3_INDEX(right,0,jj); + for(kk=1; kk<3; ++kk) + { + sum += (MTX3_INDEX(left,ii,kk)*MTX3_INDEX(right,kk,jj)); + } + MTX3_INDEX(result,ii,jj) = sum; + } + } + return m; + } + #undef MTX3_INDEX + + inline float3 operator*(const float3 &v) const + { + float3 a; + + a.x = (r0*v); + a.y = (r1*v); + a.z = (r2*v); + + return a; + } + + /***************************************\ + | Basic math operations with scalars | + \***************************************/ + float3x3 operator+(float f) const + { + return float3x3(r0+f, r1+f, r2+f); + } + + float3 const &operator[](int index) const { + //assert( ( index >= 0 ) && ( index < 3 ) ); + return (&r0)[index]; + } + + float3 &operator[](int index) { + //assert( ( index >= 0 ) && ( index < 3 ) ); + return (&r0)[index]; + } + + const float3x3 &operator+=(float f) + { + r0 += f; + r1 += f; + r2 += f; + return *this; + } + + + friend float3 operator*(const float3 &vec, const float3x3 &mat) { + return mat * vec; + } + + friend float3x3 operator*(const float a, const float3x3 &mat) { + return mat * a; + } + + friend float3 &operator*=(float3 &vec, const float3x3 &mat) { + float x = mat[0].x * vec.x + mat[1].x * vec.y + mat[2].x * vec.z; + float y = mat[0].y * vec.x + mat[1].y * vec.y + mat[2].y * vec.z; + vec.z = mat[0].z * vec.x + mat[1].z * vec.y + mat[2].z * vec.z; + vec.x = x; + vec.y = y; + return vec; + } + + float3x3 &operator*=(const float3x3 &a) { + int i, j; + const float *m2Ptr; + float *m1Ptr, dst[3]; + + m1Ptr = reinterpret_cast(this); + m2Ptr = reinterpret_cast(&a); + + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) { + dst[j] = m1Ptr[0] * m2Ptr[0 * 3 + j] + + m1Ptr[1] * m2Ptr[1 * 3 + j] + + m1Ptr[2] * m2Ptr[2 * 3 + j]; + } + m1Ptr[0] = dst[0]; m1Ptr[1] = dst[1]; m1Ptr[2] = dst[2]; + m1Ptr += 3; + } + return *this; + } + + float3x3 operator-(float f) const + { + return float3x3(r0-f, r1-f, r2-f); + } + bool Compare(const float3x3 &a) const { + if (r0.Compare(a[0]) && + r1.Compare(a[1]) && + r2.Compare(a[2])) { + return true; + } + return false; + } + + bool Compare(const float3x3 &a, const float epsilon) const { + if (r0.Compare(a[0], epsilon) && + r1.Compare(a[1], epsilon) && + r2.Compare(a[2], epsilon)) { + return true; + } + return false; + } + + const float3x3 &operator-=(float f) + { + r0 -= f; + r1 -= f; + r2 -= f; + return *this; + } + float3x3 operator*(float f) const + { + return float3x3(r0*f, r1*f, r2*f); + } + const float3x3 &operator*=(float f) + { + r0 *= f; + r1 *= f; + r2 *= f; + return *this; + } + float3x3 operator/(float f) const + { + return float3x3(r0/f, r1/f, r2/f); + } + const float3x3 &operator/=(float f) + { + r0 /= f; + r1 /= f; + r2 /= f; + return *this; + } + + /***************************************\ + | Other math | + \***************************************/ + // Equality + bool operator==(const float3x3 &r) const + { + return r0 == r.r0 && r1 == r.r1 && r2 == r.r2; + } + bool operator!=(const float3x3 &r) const + { + return !(*this == r); + } + + float determinant() const + { + float f0 = r0.x * (r1.y*r2.z-r2.y*r1.z); + float f1 = r0.y * -(r1.x*r2.z-r2.x*r1.z); + float f2 = r0.z * (r1.x*r2.y-r2.x*r1.y); + + return f0 + f1 + f2; + } + + void transpose(void) + { + Swap(r0.y, r1.x); + Swap(r0.z, r2.x); + Swap(r1.z, r2.y); + } + + void invert(void) + { + float det = determinant(); + float3x3 inv; + + inv.r0.x = (r1.y*r2.z) - (r1.z*r2.y); + inv.r0.y = -((r1.x*r2.z) - (r1.z*r2.x)); + inv.r0.z = (r1.x*r2.y) - (r1.y*r2.x); + + inv.r1.x = -((r0.y*r2.z) - (r0.z*r2.y)); + inv.r1.y = (r0.x*r2.z) - (r0.z*r2.x); + inv.r1.z = -((r0.x*r2.y) - (r0.y*r2.x)); + + inv.r2.x = (r0.y*r1.z) - (r0.z*r1.y); + inv.r2.y = -((r0.x*r1.z) - (r0.z*r1.x)); + inv.r2.z = (r0.x*r1.y) - (r0.y*r1.x); + + inv.transpose(); + inv /= det; + + *this = inv; + } +}; +inline float3x3 float3x3Identity(void) +{ + return float3x3(1,0,0, + 0,1,0, + 0,0,1); +} +inline float determinant(const float3x3&m) +{ + return m.determinant(); +} + +inline float3x3 transpose(const float3x3 &m) +{ + float3x3 ret = m; + ret.transpose(); + return ret; +} + +inline float3x3 inverse(const float3x3 &m) +{ + float3x3 ret = m; + ret.invert(); + return ret; +} + +inline float3x3 float3x3RotationX(float rad) +{ + float c = cosf( rad ); + float s = sinf( rad ); + float3x3 m( 1.0f, 0.0f, 0.0f, + 0.0f, c, s, + 0.0f, -s, c ); + return m; +} +inline float3x3 float3x3RotationY(float rad) +{ + float c = cosf( rad ); + float s = sinf( rad ); + float3x3 m( c, 0.0f, -s, + 0.0f, 1.0f, 0.0f, + s, 0.0f, c ); + return m; +} +inline float3x3 float3x3RotationZ(float rad) +{ + + float c = cosf( rad ); + float s = sinf( rad ); + float3x3 m( c, s, 0.0f, + -s, c, 0.0f, + 0.0f, 0.0f, 1.0f ); + return m; +} + +inline float3x3 float3x3RotationAxis(const float3 &axis, float rad ) +{ + float3 normAxis = normalize(axis); + float c = cosf(rad); + float s = sinf(rad); + float t = 1 - c; + + float x = normAxis.x; + float y = normAxis.y; + float z = normAxis.z; + + float3x3 m; + + m.r0.x = (t * x * x) + c; + m.r0.y = (t * x * y) + s * z; + m.r0.z = (t * x * z) - s * y; + + m.r1.x = (t * x * y) - (s * z); + m.r1.y = (t * y * y) + c; + m.r1.z = (t * y * z) + (s * x); + + m.r2.x = (t * x * z) + (s * y); + m.r2.y = (t * y * z) - (s * x); + m.r2.z = (t * z * z) + c; + + return m; +} + +inline float3x3 float3x3Scale(float x, float y, float z) +{ + return float3x3(x,0,0, + 0,y,0, + 0,0,z); +} + +/**************************************\ +float4x4 +\**************************************/ +struct float4x4 +{ + struct + { + float4 r0; + float4 r1; + float4 r2; + float4 r3; + }; + + /***************************************\ + | Constructors | + \***************************************/ + float4x4() {} + explicit float4x4(float f) : r0(f), r1(f), r2(f), r3(f) { } + explicit float4x4(float* f) : r0(f+0), r1(f+4), r2(f+8), r3(f+12) { } + float4x4(const float4 &_r0, const float4 &_r1, const float4 &_r2, const float4 &_r3) : r0(_r0), r1(_r1), r2(_r2), r3(_r3) { } + float4x4(float _m00, float _m01, float _m02, float _m03, + float _m10, float _m11, float _m12, float _m13, + float _m20, float _m21, float _m22, float _m23, + float _m30, float _m31, float _m32, float _m33) + : r0(_m00,_m01,_m02,_m03) + , r1(_m10,_m11,_m12,_m13) + , r2(_m20,_m21,_m22,_m23) + , r3(_m30,_m31,_m32,_m33) + { + } + float4x4(const float3x3 &m) + : r0(m.r0, 0.0f) + , r1(m.r1, 0.0f) + , r2(m.r2, 0.0f) + , r3(0.0f, 0.0f, 0.0f, 1.0f) + { + } + const float4x4 &operator=(const float4x4 &m) + { + r0 = m.r0; + r1 = m.r1; + r2 = m.r2; + r3 = m.r3; + return *this; + } + + /***************************************\ + | Basic math operations | + \***************************************/ + + #define MTX4_INDEX(f,r,c) ((f)[(r*4)+c]) + inline float4x4 operator*(const float4x4 &r) const + { + float4x4 m(1,0,0,0, + 0,1,0,0, + 0,0,1,0, + 0,0,0,1); + + const float* right = (const float*)&this->r0; + const float* left = (const float*)&r.r0; + float* result = (float*)&m; + + int ii, jj, kk; + for(ii=0; ii<4; ++ii) /* row */ + { + for(jj=0; jj<4; ++jj) /* column */ + { + float sum = MTX4_INDEX(left,ii,0)*MTX4_INDEX(right,0,jj); + for(kk=1; kk<4; ++kk) + { + sum += (MTX4_INDEX(left,ii,kk)*MTX4_INDEX(right,kk,jj)); + } + MTX4_INDEX(result,ii,jj) = sum; + } + } + return m; + } + #undef MTX4_INDEX + + inline float4 operator*(const float4 &v) const + { + float4 a; + + a.x = (r0*v).hadd(); + a.y = (r1*v).hadd(); + a.z = (r2*v).hadd(); + a.w = (r3*v).hadd(); + + return a; + } + + /***************************************\ + | Basic math operations with scalars | + \***************************************/ + float4x4 operator+(float f) const + { + return float4x4(r0+f, r1+f, r2+f, r3+f); + } + const float4x4 &operator+=(float f) + { + r0 += f; + r1 += f; + r2 += f; + r3 += f; + return *this; + } + float4x4 operator-(float f) const + { + return float4x4(r0-f, r1-f, r2-f, r3-f); + } + const float4x4 &operator-=(float f) + { + r0 -= f; + r1 -= f; + r2 -= f; + r3 -= f; + return *this; + } + float4x4 operator*(float f) const + { + return float4x4(r0*f, r1*f, r2*f, r3*f); + } + const float4x4 &operator*=(float f) + { + r0 *= f; + r1 *= f; + r2 *= f; + r3 *= f; + return *this; + } + float4x4 operator/(float f) const + { + return float4x4(r0/f, r1/f, r2/f, r3/f); + } + const float4x4 &operator/=(float f) + { + r0 /= f; + r1 /= f; + r2 /= f; + r3 /= f; + return *this; + } + + /***************************************\ + | Other math | + \***************************************/ + // Equality + bool operator==(const float4x4 &r) const + { + return r0 == r.r0 && r1 == r.r1 && r2 == r.r2 && r3 == r.r3; + } + bool operator!=(const float4x4 &r) const + { + return !(*this == r); + } + + float determinant() const + { + float det = 0.0f; + + float3x3 a( r1.y,r1.z,r1.w, + r2.y,r2.z,r2.w, + r3.y,r3.z,r3.w); + + float3x3 b( r1.x,r1.z,r1.w, + r2.x,r2.z,r2.w, + r3.x,r3.z,r3.w); + + float3x3 c( r1.x,r1.y,r1.w, + r2.x,r2.y,r2.w, + r3.x,r3.y,r3.w); + + float3x3 d( r1.x,r1.y,r1.z, + r2.x,r2.y,r2.z, + r3.x,r3.y,r3.z); + + + det += r0.x * a.determinant(); + + det -= r0.y * b.determinant(); + + det += r0.z * c.determinant(); + + det -= r0.w * d.determinant(); + + return det; + } + + void transpose(void) + { + Swap(r0.y, r1.x); + Swap(r0.z, r2.x); + Swap(r0.w, r3.x); + Swap(r1.z, r2.y); + Swap(r1.w, r3.y); + Swap(r2.w, r3.z); + } + + void invert(void) + { + float4x4 ret; + float recip; + + /* temp matrices */ + + /* row 1 */ + float3x3 a( r1.y,r1.z,r1.w, + r2.y,r2.z,r2.w, + r3.y,r3.z,r3.w); + + float3x3 b( r1.x,r1.z,r1.w, + r2.x,r2.z,r2.w, + r3.x,r3.z,r3.w); + + float3x3 c( r1.x,r1.y,r1.w, + r2.x,r2.y,r2.w, + r3.x,r3.y,r3.w); + + float3x3 d( r1.x,r1.y,r1.z, + r2.x,r2.y,r2.z, + r3.x,r3.y,r3.z); + + /* row 2 */ + float3x3 e( r0.y,r0.z,r0.w, + r2.y,r2.z,r2.w, + r3.y,r3.z,r3.w); + + float3x3 f( r0.x,r0.z,r0.w, + r2.x,r2.z,r2.w, + r3.x,r3.z,r3.w); + + float3x3 g( r0.x,r0.y,r0.w, + r2.x,r2.y,r2.w, + r3.x,r3.y,r3.w); + + float3x3 h( r0.x,r0.y,r0.z, + r2.x,r2.y,r2.z, + r3.x,r3.y,r3.z); + + + /* row 3 */ + float3x3 i( r0.y,r0.z,r0.w, + r1.y,r1.z,r1.w, + r3.y,r3.z,r3.w); + + float3x3 j( r0.x,r0.z,r0.w, + r1.x,r1.z,r1.w, + r3.x,r3.z,r3.w); + + float3x3 k( r0.x,r0.y,r0.w, + r1.x,r1.y,r1.w, + r3.x,r3.y,r3.w); + + float3x3 l( r0.x,r0.y,r0.z, + r1.x,r1.y,r1.z, + r3.x,r3.y,r3.z); + + + /* row 4 */ + float3x3 m( r0.y, r0.z, r0.w, + r1.y, r1.z, r1.w, + r2.y, r2.z, r2.w); + + float3x3 n( r0.x, r0.z, r0.w, + r1.x, r1.z, r1.w, + r2.x, r2.z, r2.w); + + float3x3 o( r0.x,r0.y,r0.w, + r1.x,r1.y,r1.w, + r2.x,r2.y,r2.w); + + float3x3 p( r0.x,r0.y,r0.z, + r1.x,r1.y,r1.z, + r2.x,r2.y,r2.z); + + /* row 1 */ + ret.r0.x = a.determinant(); + + ret.r0.y = -b.determinant(); + + ret.r0.z = c.determinant(); + + ret.r0.w = -d.determinant(); + + /* row 2 */ + ret.r1.x = -e.determinant(); + + ret.r1.y = f.determinant(); + + ret.r1.z = -g.determinant(); + + ret.r1.w = h.determinant(); + + /* row 3 */ + ret.r2.x = i.determinant(); + + ret.r2.y = -j.determinant(); + + ret.r2.z = k.determinant(); + + ret.r2.w = -l.determinant(); + + /* row 4 */ + ret.r3.x = -m.determinant(); + + ret.r3.y = n.determinant(); + + ret.r3.z = -o.determinant(); + + ret.r3.w = p.determinant(); + + ret.transpose(); + recip = 1.0f/determinant(); + ret *= recip; + + *this = ret; + } + + // Axis access + float3 getXAxis(void) const + { + return r0; + } + float3 getYAxis(void) const + { + return r1; + } + float3 getZAxis(void) const + { + return r2; + } + float3 getPosition(void) const + { + return float3(r3); + } + + void orthonormalize(void) + { + float3 x = getXAxis(); + float3 y = getYAxis(); + float3 z; + + x.normalize(); + z = normalize(cross3(x, y)); + y = normalize(cross3(z, x)); + + r0 = float4(x, 0.0f); + r1 = float4(y, 0.0f); + r2 = float4(z, 0.0f); + } +}; + +inline float3x3::float3x3(const float4x4 &m) + : r0(m.r0) + , r1(m.r1) + , r2(m.r2) +{ +} +inline float determinant(const float4x4&m) +{ + return m.determinant(); +} +inline float4x4 float4x4Identity(void) +{ + return float4x4(1,0,0,0, + 0,1,0,0, + 0,0,1,0, + 0,0,0,1); +} +inline float4x4 transpose(const float4x4 &m) +{ + float4x4 ret = m; + ret.transpose(); + return ret; +} + +inline float4x4 inverse(const float4x4 &m) +{ + float4x4 ret = m; + ret.invert(); + return ret; +} + +inline float4x4 float4x4RotationX(float rad) +{ + float c = cosf( rad ); + float s = sinf( rad ); + float4x4 m( 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, c, s, 0.0f, + 0.0f, -s, c, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); + return m; +} +inline float4x4 float4x4RotationY(float rad) +{ + float c = cosf( rad ); + float s = sinf( rad ); + float4x4 m( c, 0.0f, -s, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + s, 0.0f, c, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); + return m; +} +inline float4x4 float4x4RotationZ(float rad) +{ + + float c = cosf( rad ); + float s = sinf( rad ); + float4x4 m( c, s, 0.0f, 0.0f, + -s, c, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); + return m; +} + +inline float4x4 float4x4RotationAxis(const float3 &axis, float rad ) +{ + float3 normAxis = normalize(axis); + float c = cosf(rad); + float s = sinf(rad); + float t = 1 - c; + + float x = normAxis.x; + float y = normAxis.y; + float z = normAxis.z; + + float4x4 m = float4x4Identity(); + + m.r0.x = (t * x * x) + c; + m.r0.y = (t * x * y) + s * z; + m.r0.z = (t * x * z) - s * y; + + m.r1.x = (t * x * y) - (s * z); + m.r1.y = (t * y * y) + c; + m.r1.z = (t * y * z) + (s * x); + + m.r2.x = (t * x * z) + (s * y); + m.r2.y = (t * y * z) - (s * x); + m.r2.z = (t * z * z) + c; + + return m; +} + +inline float4x4 float4x4Scale(float x, float y, float z) +{ + return float4x4(x,0,0,0, + 0,y,0,0, + 0,0,z,0, + 0,0,0,1); +} + +inline float4x4 float4x4Translation(float x, float y, float z) +{ + float4x4 m = float4x4Identity(); + m.r3.x = x; + m.r3.y = y; + m.r3.z = z; + return m; +} +inline float4x4 float4x4Translation(const float3 &v) +{ + return float4x4Translation(v.x, v.y, v.z); +} + +inline float4x4 float4x4PerspectiveFovLH(float fov, float aspect, float nearPlane, float farPlane) +{ + float width = tanf(fov * 0.5f); + float height = width / aspect; + float diff = farPlane-nearPlane; + float div = farPlane / diff; + + float4x4 m( + 1.0f/width, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f/height, 0.0f, 0.0f, + 0.0f, 0.0f, div, 1.0f, + 0.0f, 0.0f, -nearPlane*div, 0.0f + ); + return m; +} +inline float4x4 float4x4PerspectiveFovRH(float fov, float aspect, float nearPlane, float farPlane) +{ + float4x4 m = float4x4Identity(); + + float f = tanf(kPiDiv2 - (fov/2.0f)); + float diff = nearPlane-farPlane; + float div = farPlane / diff; + + m.r0.x = f/aspect; + m.r1.y = f; + m.r2.z = div; + m.r2.w = -1; + m.r3.z = nearPlane * div; + m.r3.w = 0; + return m; +} +inline float4x4 float4x4PerspectiveLH(float width, float height, float nearPlane, float farPlane) +{ + float4x4 m = float4x4Identity(); + + m.r0.x = 2*nearPlane/width; + m.r1.y = 2*nearPlane/height; + m.r2.z = farPlane/(farPlane-nearPlane); + m.r2.w = 1; + m.r3.z = nearPlane*farPlane/(nearPlane-farPlane); + m.r3.w = 0; + return m; +} +inline float4x4 float4x4PerspectiveRH(float width, float height, float nearPlane, float farPlane) +{ + float4x4 m = float4x4Identity(); + + m.r0.x = 2*nearPlane/width; + m.r1.y = 2*nearPlane/height; + m.r2.z = farPlane/(nearPlane-farPlane); + m.r2.w = -1; + m.r3.z = nearPlane*farPlane/(nearPlane-farPlane); + m.r3.w = 0; + return m; +} +inline float4x4 float4x4OrthographicOffCenterLH(float left, float right, float top, float bottom, float nearPlane, float farPlane) +{ + float4x4 m = float4x4Identity(); + + float diff = farPlane-nearPlane; + + m.r0.x = 2.0f/(right-left); + m.r1.y = 2.0f/(top-bottom); + m.r2.z = 1.0f/diff; + m.r3.x = -((left+right)/(right-left)); + m.r3.y = -((top+bottom)/(top-bottom)); + m.r3.z = -nearPlane/diff; + + return m; +} +inline float4x4 float4x4OrthographicOffCenterRH(float left, float right, float top, float bottom, float nearPlane, float farPlane) +{ + float4x4 m = float4x4Identity(); + float diff = nearPlane-farPlane; + + m.r0.x = 2.0f/(right-left); + m.r1.y = 2.0f/(top-bottom); + m.r2.z = 1.0f/diff; + m.r3.x = -((left+right)/(right-left)); + m.r3.y = -((top+bottom)/(top-bottom)); + m.r3.z = nearPlane/diff; + + return m; +} +inline float4x4 float4x4OrthographicLH(float width, float height, float nearPlane, float farPlane) +{ + float halfWidth = width/2.0f; + float halfHeight = height/2.0f; + + return float4x4OrthographicOffCenterLH(-halfWidth, halfWidth, halfHeight, -halfHeight, nearPlane, farPlane); +} +inline float4x4 float4x4OrthographicRH(float width, float height, float nearPlane, float farPlane) +{ + float halfWidth = width/2.0f; + float halfHeight = height/2.0f; + + return float4x4OrthographicOffCenterRH(-halfWidth, halfWidth, halfHeight, -halfHeight, nearPlane, farPlane); +} + +/**************************************\ +Quaternion +\**************************************/ +struct quaternion : public float4 +{ + + /***************************************\ + | Constructors | + \***************************************/ + quaternion() {} + explicit quaternion(float f) : float4(f) { } + explicit quaternion(float _x, float _y, float _z, float _w) : float4(_x,_y,_z,_w) { } + explicit quaternion(float* f) : float4(f) { } + quaternion(const quaternion &v) : float4(v) { } + quaternion(const float3 &v, float _w) + { + float3 norm = ::normalize(v); + float a = _w*0.5f; + float s = sinf(a); + x = norm.x*s; + y = norm.y*s; + z = norm.z*s; + w = cosf(a); + } + const quaternion &operator=(const quaternion &v) + { + x = v.x; + y = v.y; + z = v.z; + w = v.w; + return *this; + } + + /* Methods */ + float3 getXAxis(void) const + { + float3 ret( 1-2*(y*y+z*z), + 2*(x*y+w*z), + 2*(x*z-y*w)); + return ret; + } + float3 getYAxis(void) const + { + float3 ret( 2*(x*y-z*w), + 1-2*(x*x+z*z), + 2*(y*z+x*w)); + return ret; + } + float3 getZAxis(void) const + { + float3 ret( 2*(x*z+y*w), + 2*(y*z-x*w), + 1-2*(x*x+y*y)); + return ret; + } + + float3x3 getMatrix(void) const + { + quaternion q = *this; + q.normalize(); + float xx = q.x * q.x; + float yy = q.y * q.y; + float zz = q.z * q.z; + + float xy = q.x * q.y; + float zw = q.z * q.w; + float xz = q.x * q.z; + float yw = q.y * q.w; + float yz = q.y * q.z; + float xw = q.x * q.w; + + float3x3 ret(1 - 2*(yy+zz), 2*(xy+zw), 2*(xz-yw), + 2*(xy-zw), 1 - 2*(xx+zz), 2*(yz+xw), + 2*(xz+yw), 2*(yz-xw), 1 - 2*(xx+yy) ); + return ret; + } + + quaternion conjugate(void) const + { + return quaternion(-x, -y, -z, w); + } + quaternion inverse(void) const + { + // Note: Only normalized quaternions are supportted at the moment + quaternion q = *this; + q.normalize(); + return q.conjugate(); + } +}; + +inline quaternion quaternionMultiply(const quaternion &l, const quaternion &r) +{ + quaternion q( r.w*l.x + r.x*l.w + r.y*l.z - r.z*l.y, + r.w*l.y + r.y*l.w + r.z*l.x - r.x*l.z, + r.w*l.z + r.z*l.w + r.x*l.y - r.y*l.x, + r.w*l.w - r.x*l.x - r.y*l.y - r.z*l.z ); + return q; +} + +inline quaternion quaternionIdentity(void) +{ + return quaternion(0.0f, 0.0f, 0.0f, 1.0f); +} + +inline float3 Min( float3 &v0, float3 &v1 ) +{ + float3 result; + result.x = v0.x < v1.x ? v0.x : v1.x; + result.y = v0.y < v1.y ? v0.y : v1.y; + result.z = v0.z < v1.z ? v0.z : v1.z; + return result; +} + +inline float3 Max( float3 &v0, float3 &v1 ) +{ + float3 result; + result.x = v0.x > v1.x ? v0.x : v1.x; + result.y = v0.y > v1.y ? v0.y : v1.y; + result.z = v0.z > v1.z ? v0.z : v1.z; + return result; +} + +inline float4 Min( float4 &v0, float4 &v1 ) +{ + float4 result; + result.x = v0.x < v1.x ? v0.x : v1.x; + result.y = v0.y < v1.y ? v0.y : v1.y; + result.z = v0.z < v1.z ? v0.z : v1.z; + result.w = v0.w < v1.w ? v0.w : v1.w; + return result; +} + +inline float4 Max( float4 &v0, float4 &v1 ) +{ + float4 result; + result.x = v0.x > v1.x ? v0.x : v1.x; + result.y = v0.y > v1.y ? v0.y : v1.y; + result.z = v0.z > v1.z ? v0.z : v1.z; + result.w = v0.w > v1.w ? v0.w : v1.w; + return result; +} + +inline float4 operator*(const float4 &v, const float4x4 &m) +{ + float4 result; + + result = m.r0 * v.x; + result += m.r1 * v.y; + result += m.r2 * v.z; + result += m.r3 * v.w; + + return result; +} + +/* +============================ +CreateLookAtMatrix +============================ +*/ +inline float4x4 CreateLookAtMatrix(float3 cameraPosition, float3 cameraTarget, float3 cameraUpVector) +{ + float3 vector3_1 = (cameraPosition - cameraTarget).normalize(); + float3 vector3_2; + + vector3_2.Cross(cameraUpVector, vector3_1); + vector3_2.NormalizeSelf(); + + float3 vector1; + vector1.Cross(vector3_1, vector3_2); + + float4x4 matrix; + matrix.r0.x = vector3_2.x; + matrix.r0.y = vector1.x; + matrix.r0.z = vector3_1.x; + matrix.r0.w = 0.0f; + matrix.r1.x = vector3_2.y; + matrix.r1.y = vector1.y; + matrix.r1.z = vector3_1.y; + matrix.r1.w = 0.0f; + matrix.r2.x = vector3_2.z; + matrix.r2.y = vector1.z; + matrix.r2.z = vector3_1.z; + matrix.r2.w = 0.0f; + matrix.r3.x = -float3::Dot(vector3_2, cameraPosition); + matrix.r3.y = -float3::Dot(vector1, cameraPosition); + matrix.r3.z = -float3::Dot(vector3_1, cameraPosition); + matrix.r3.w = 1.0f; + + return matrix; +} + +__inline float3 abs(const float3 xyz) +{ + float3 result(fabs(xyz.x), fabs(xyz.y), fabs(xyz.z)); + return result; +} + + +__inline float4 abs(const float4 xyz) +{ + float4 result(fabs(xyz.x), fabs(xyz.y), fabs(xyz.z), fabs(xyz.w)); + return result; +} + +__inline float3 fabs(const float3 xyz) +{ + float3 result(fabs(xyz.x), fabs(xyz.y), fabs(xyz.z)); + return result; +} + + +__inline float4 fabs(const float4 xyz) +{ + float4 result(fabs(xyz.x), fabs(xyz.y), fabs(xyz.z), fabs(xyz.w)); + return result; +} + +static const float Identity[16] = { + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0 +}; + +__inline void _math_matrix_rotate(float4x4 &matrix, float angle, float x, float y, float z) +{ + float xx, yy, zz, xy, yz, zx, xs, ys, zs, one_c, s, c; + float m[16]; + bool optimized; + #define M_PI 3.14159265358979323846 + s = sinf(angle * M_PI / 180.0); + c = cosf(angle * M_PI / 180.0); + + memcpy(m, Identity, sizeof(Identity)); + optimized = false; + +#define M(row,col) m[col*4+row] + + if (x == 0.0F) { + if (y == 0.0F) { + if (z != 0.0F) { + optimized = true; + /* rotate only around z-axis */ + M(0, 0) = c; + M(1, 1) = c; + if (z < 0.0F) { + M(0, 1) = s; + M(1, 0) = -s; + } + else { + M(0, 1) = -s; + M(1, 0) = s; + } + } + } + else if (z == 0.0F) { + optimized = true; + /* rotate only around y-axis */ + M(0, 0) = c; + M(2, 2) = c; + if (y < 0.0F) { + M(0, 2) = -s; + M(2, 0) = s; + } + else { + M(0, 2) = s; + M(2, 0) = -s; + } + } + } + else if (y == 0.0F) { + if (z == 0.0F) { + optimized = true; + /* rotate only around x-axis */ + M(1, 1) = c; + M(2, 2) = c; + if (x < 0.0F) { + M(1, 2) = s; + M(2, 1) = -s; + } + else { + M(1, 2) = -s; + M(2, 1) = s; + } + } + } + + if (!optimized) { + const float mag = sqrtf(x * x + y * y + z * z); + + if (mag <= 1.0e-4F) { + /* no rotation, leave mat as-is */ + return; + } + + x /= mag; + y /= mag; + z /= mag; + + + /* + * Arbitrary axis rotation matrix. + * + * This is composed of 5 matrices, Rz, Ry, T, Ry', Rz', multiplied + * like so: Rz * Ry * T * Ry' * Rz'. T is the final rotation + * (which is about the X-axis), and the two composite transforms + * Ry' * Rz' and Rz * Ry are (respectively) the rotations necessary + * from the arbitrary axis to the X-axis then back. They are + * all elementary rotations. + * + * Rz' is a rotation about the Z-axis, to bring the axis vector + * into the x-z plane. Then Ry' is applied, rotating about the + * Y-axis to bring the axis vector parallel with the X-axis. The + * rotation about the X-axis is then performed. Ry and Rz are + * simply the respective inverse transforms to bring the arbitrary + * axis back to its original orientation. The first transforms + * Rz' and Ry' are considered inverses, since the data from the + * arbitrary axis gives you info on how to get to it, not how + * to get away from it, and an inverse must be applied. + * + * The basic calculation used is to recognize that the arbitrary + * axis vector (x, y, z), since it is of unit length, actually + * represents the sines and cosines of the angles to rotate the + * X-axis to the same orientation, with theta being the angle about + * Z and phi the angle about Y (in the order described above) + * as follows: + * + * cos ( theta ) = x / sqrt ( 1 - z^2 ) + * sin ( theta ) = y / sqrt ( 1 - z^2 ) + * + * cos ( phi ) = sqrt ( 1 - z^2 ) + * sin ( phi ) = z + * + * Note that cos ( phi ) can further be inserted to the above + * formulas: + * + * cos ( theta ) = x / cos ( phi ) + * sin ( theta ) = y / sin ( phi ) + * + * ...etc. Because of those relations and the standard trigonometric + * relations, it is pssible to reduce the transforms down to what + * is used below. It may be that any primary axis chosen will give the + * same results (modulo a sign convention) using thie method. + * + * Particularly nice is to notice that all divisions that might + * have caused trouble when parallel to certain planes or + * axis go away with care paid to reducing the expressions. + * After checking, it does perform correctly under all cases, since + * in all the cases of division where the denominator would have + * been zero, the numerator would have been zero as well, giving + * the expected result. + */ + + xx = x * x; + yy = y * y; + zz = z * z; + xy = x * y; + yz = y * z; + zx = z * x; + xs = x * s; + ys = y * s; + zs = z * s; + one_c = 1.0F - c; + + /* We already hold the identity-matrix so we can skip some statements */ + M(0, 0) = (one_c * xx) + c; + M(0, 1) = (one_c * xy) - zs; + M(0, 2) = (one_c * zx) + ys; + /* M(0,3) = 0.0F; */ + + M(1, 0) = (one_c * xy) + zs; + M(1, 1) = (one_c * yy) + c; + M(1, 2) = (one_c * yz) - xs; + /* M(1,3) = 0.0F; */ + + M(2, 0) = (one_c * zx) - ys; + M(2, 1) = (one_c * yz) + xs; + M(2, 2) = (one_c * zz) + c; + /* M(2,3) = 0.0F; */ + + /* + M(3,0) = 0.0F; + M(3,1) = 0.0F; + M(3,2) = 0.0F; + M(3,3) = 1.0F; + */ + } +#undef M + float4x4 _internalMatrix(&m[0]); + //_internalMatrix.r[0] = XMVectorSet(m[0], m[1], m[2], m[3]); + //_internalMatrix.r[1] = XMVectorSet(m[4], m[5], m[6], m[7]); + //_internalMatrix.r[2] = XMVectorSet(m[8], m[9], m[10], m[11]); + //_internalMatrix.r[3] = XMVectorSet(m[12], m[13], m[14], m[15]); + float4x4 temp = matrix * _internalMatrix; + matrix = temp; + + //matmul4(mat, mat, m); + //matrix_multf(mat, m, MAT_FLAG_ROTATION); +} + +static void glhFrustumf2(float *matrix, float left, float right, float bottom, float top, float znear, float zfar) +{ + float temp, temp2, temp3, temp4; + temp = 2.0 * znear; + temp2 = right - left; + temp3 = top - bottom; + temp4 = zfar - znear; + matrix[0] = temp / temp2; + matrix[1] = 0.0; + matrix[2] = 0.0; + matrix[3] = 0.0; + matrix[4] = 0.0; + matrix[5] = temp / temp3; + matrix[6] = 0.0; + matrix[7] = 0.0; + matrix[8] = (right + left) / temp2; + matrix[9] = (top + bottom) / temp3; + matrix[10] = (-zfar - znear) / temp4; + matrix[11] = -1.0; + matrix[12] = 0.0; + matrix[13] = 0.0; + matrix[14] = (-temp * zfar) / temp4; + matrix[15] = 0.0; +} + + +static void glhPerspectivef2(float *matrix, float fovyInDegrees, float aspectRatio, float znear, float zfar) +{ + float ymax, xmax; + float temp, temp2, temp3, temp4; + ymax = znear * tanf(fovyInDegrees * M_PI / 360.0); + //ymin = -ymax; + //xmin = -ymax * aspectRatio; + xmax = ymax * aspectRatio; + glhFrustumf2(matrix, -xmax, xmax, -ymax, ymax, znear, zfar); +} + +#endif // #ifndef __CPUTMath_h__ \ No newline at end of file From e3c5e96c81d07a615a8562a5551bf9ca5c099886 Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Fri, 19 Jun 2020 20:57:48 -0700 Subject: [PATCH 08/10] Removed CPU side memory buffer for textures --- code/REDALERT/Image.h | 12 ++++++------ code/REDALERT/WINSTUB.CPP | 27 ++++++++++++++++----------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/code/REDALERT/Image.h b/code/REDALERT/Image.h index 3631a73..51700f2 100644 --- a/code/REDALERT/Image.h +++ b/code/REDALERT/Image.h @@ -15,19 +15,19 @@ struct Image_t { int height; int renderwidth; int renderheight; - unsigned char* buffer; + //unsigned char* buffer[MAX_HOUSE_COLORS][MAX_IMAGE_FRAMES]; }; __forceinline Image_t::Image_t() { - buffer = NULL; +// buffer = NULL; numAnimFrames = 0; } __forceinline Image_t::~Image_t() { - if (buffer) { - delete buffer; - buffer = NULL; - } + //if (buffer) { + // delete buffer; + // buffer = NULL; + //} } Image_t* Image_LoadImage(const char* name, bool loadAnims = false, bool loadHouseColor = false); diff --git a/code/REDALERT/WINSTUB.CPP b/code/REDALERT/WINSTUB.CPP index 049e45f..68ccb63 100644 --- a/code/REDALERT/WINSTUB.CPP +++ b/code/REDALERT/WINSTUB.CPP @@ -93,6 +93,8 @@ void Show_OldFrameBuffer(bool show) { std::vector loaded_images; +unsigned char* Draw_Dropsample(const unsigned char* in, int inwidth, int inheight, int outwidth, int outheight); + /* ================ return a hash value for the filename @@ -133,6 +135,11 @@ static bool Image_loadHDImage(Image_t *image, const char* name, int houseid, int ILubyte* Data = ilGetData(); if (Bpp == 4 && houseId != -1) { + //Buffer_Enable_HD_Texture(true); + //Data = Draw_Dropsample(Data, Width, Height, Width / 2, Height / 2); + //Buffer_Enable_HD_Texture(false); + //Width = Width / 2; + //Height = Height / 2; for (int i = 0; i < Width * Height; i++) { RenderTeamPalette(&Data[i * 4], *houseColors[houseId]); } @@ -169,11 +176,8 @@ static bool Image_loadHDImage(Image_t *image, const char* name, int houseid, int image->namehash = hash; image->renderwidth = image->width = Width; image->renderheight = image->height = Height; - image->buffer = new unsigned char[Width * Height * Bpp]; - - if (houseid == 0 && animid == 0) { - memcpy(image->buffer, Data, Width * Height * Bpp); - } + //image->buffer[houseid][animid] = new unsigned char[Width * Height * Bpp]; + //memcpy(image->buffer[houseid][animid], Data, Width * Height * Bpp); ilDeleteImages(1, &ImageName); return true; @@ -242,7 +246,7 @@ Image_t* Image_CreateImageFrom8Bit(const char* name, int Width, int Height, unsi unsigned char* ccpalete = (unsigned char*)CCPalette.Get_Data(); Image_t* image = new Image_t(); - image->buffer = new unsigned char[Width * Height * 4]; + unsigned char *buffer = new unsigned char[Width * Height * 4]; for (int i = 0; i < Width * Height; i++) { unsigned char c = data[i]; @@ -266,10 +270,10 @@ Image_t* Image_CreateImageFrom8Bit(const char* name, int Width, int Height, unsi } - image->buffer[(i * 4) + 0] = r; - image->buffer[(i * 4) + 1] = g; - image->buffer[(i * 4) + 2] = b; - image->buffer[(i * 4) + 3] = a; + buffer[(i * 4) + 0] = r; + buffer[(i * 4) + 1] = g; + buffer[(i * 4) + 2] = b; + buffer[(i * 4) + 3] = a; } @@ -277,7 +281,7 @@ Image_t* Image_CreateImageFrom8Bit(const char* name, int Width, int Height, unsi glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Width, Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image->buffer); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Width, Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); @@ -289,6 +293,7 @@ Image_t* Image_CreateImageFrom8Bit(const char* name, int Width, int Height, unsi image->renderwidth = image->width = Width; image->renderheight = image->height = Height; loaded_images.push_back(image); + delete buffer; return image; } From c511b0f9bf6d9e6e2b0a40c507429f0e187439f4 Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Fri, 19 Jun 2020 22:02:50 -0700 Subject: [PATCH 09/10] Added image cache for remaster assets. --- cache/empty.txt | 0 code/REDALERT/WINSTUB.CPP | 87 +++++++++++++++++++++++++++++++++------ 2 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 cache/empty.txt diff --git a/cache/empty.txt b/cache/empty.txt new file mode 100644 index 0000000..e69de29 diff --git a/code/REDALERT/WINSTUB.CPP b/code/REDALERT/WINSTUB.CPP index 68ccb63..ccab983 100644 --- a/code/REDALERT/WINSTUB.CPP +++ b/code/REDALERT/WINSTUB.CPP @@ -113,7 +113,64 @@ static int64_t generateHashValue(const char* fname, const int size) { return hash; } -static bool Image_loadHDImage(Image_t *image, const char* name, int houseid, int animid, int64_t hash, int houseId) { +/* +================ +Image_WriteTGA +================ +*/ +void Image_WriteTGA(const char* filename, const byte* data, int width, int height, bool flipVertical) { + byte* buffer; + int i; + int bufferSize = width * height * 4 + 18; + int imgStart = 18; + + buffer = (byte*)malloc(bufferSize); + memset(buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = width & 255; + buffer[13] = width >> 8; + buffer[14] = height & 255; + buffer[15] = height >> 8; + buffer[16] = 32; // pixel size + + if (!flipVertical) { + buffer[17] = (1 << 5); // flip bit, for normal top to bottom raster order + } + + // swap rgba to bgra + for (i = imgStart; i < bufferSize; i += 4) { + buffer[i] = data[i - imgStart + 2]; // blue + buffer[i + 1] = data[i - imgStart + 1]; // green + buffer[i + 2] = data[i - imgStart + 0]; // red + buffer[i + 3] = data[i - imgStart + 3]; // alpha + } + char temp[512]; + strcpy(temp, filename); + COM_SetExtension(temp, strlen(temp), ".tga"); + FILE* f = fopen(temp, "wb"); + fwrite(buffer, 1, bufferSize, f); + fclose(f); + + free(buffer); +} + + +static char* Image_GetGeneratedName(Image_t* image, const char* name, int houseid, int animid, int64_t hash) { + static char generatedFileName[1024]; + sprintf(generatedFileName, "cache/%d.%d.%d.tga", hash, houseid, animid); + return &generatedFileName[0]; +} + +static bool Image_loadHDImage(Image_t *image, const char* name, int houseid, int animid, int64_t hash, bool writeGenerated) { + // Check to see if we can load a generated image first. + // We only generate images that need a houseid + if (!writeGenerated && houseid >= 0) { + char* generatedImageName = Image_GetGeneratedName(image, name, houseid, animid, hash); + if (Image_loadHDImage(image, generatedImageName, houseid, animid, hash, true)) { + return true; + } + } + ILuint ImageName; ilGenImages(1, &ImageName); ilBindImage(ImageName); @@ -134,14 +191,14 @@ static bool Image_loadHDImage(Image_t *image, const char* name, int houseid, int Bpp = ilGetInteger(IL_IMAGE_BPP); ILubyte* Data = ilGetData(); - if (Bpp == 4 && houseId != -1) { + if (Bpp == 4 && houseid != -1 && !writeGenerated) { //Buffer_Enable_HD_Texture(true); //Data = Draw_Dropsample(Data, Width, Height, Width / 2, Height / 2); //Buffer_Enable_HD_Texture(false); //Width = Width / 2; //Height = Height / 2; for (int i = 0; i < Width * Height; i++) { - RenderTeamPalette(&Data[i * 4], *houseColors[houseId]); + RenderTeamPalette(&Data[i * 4], *houseColors[houseid]); } } @@ -172,14 +229,24 @@ static bool Image_loadHDImage(Image_t *image, const char* name, int houseid, int strcpy(image->name, name); - image->image[houseid][animid] = texture; + + if(houseid == -1) + image->image[0][animid] = texture; + else + image->image[houseid][animid] = texture; image->namehash = hash; image->renderwidth = image->width = Width; image->renderheight = image->height = Height; //image->buffer[houseid][animid] = new unsigned char[Width * Height * Bpp]; //memcpy(image->buffer[houseid][animid], Data, Width * Height * Bpp); + if (!writeGenerated && Bpp == 4 && houseid >= 0) { + char* generatedImageName = Image_GetGeneratedName(image, name, houseid, animid, hash); + Image_WriteTGA(generatedImageName, Data, Width, Height, false); + } + ilDeleteImages(1, &ImageName); + return true; } @@ -202,7 +269,7 @@ Image_t* Image_LoadImage(const char* name, bool loadAnims, bool loadHouseColor) if (!loadAnims) { unsigned char remap[3] = { 0, 0, 0 }; - if (!Image_loadHDImage(image, name, 0, 0, hash, -1)) { + if (!Image_loadHDImage(image, name, -1, 0, hash, false)) { delete image; return NULL; } @@ -210,19 +277,13 @@ Image_t* Image_LoadImage(const char* name, bool loadAnims, bool loadHouseColor) else { byte* palette = (byte *)CCPalette.Get_Data(); for (int i = 0; i < MAX_MPLAYER_COLORS; i++) { - unsigned char p = ColorRemaps[i].Color; - unsigned char remap[3]; - - remap[0] = palette[(p * 3) + 0]; - remap[1] = palette[(p * 3) + 1]; - remap[2] = palette[(p * 3) + 2]; - if (!Image_loadHDImage(image, name, i, 0, hash, i)) { + if (!Image_loadHDImage(image, name, i, 0, hash, false)) { delete image; return NULL; } } } - + loaded_images.push_back(image); return image; } From 5087eeb2949a82fa1773eb2885d19f95ba900064 Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Fri, 19 Jun 2020 22:45:03 -0700 Subject: [PATCH 10/10] Adjusted HD colors so the they the team colors. --- code/REDALERT/HOUSECOLOR.H | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/REDALERT/HOUSECOLOR.H b/code/REDALERT/HOUSECOLOR.H index fb81f31..13cedd6 100644 --- a/code/REDALERT/HOUSECOLOR.H +++ b/code/REDALERT/HOUSECOLOR.H @@ -40,17 +40,17 @@ struct HouseColorHD { // static HouseColorHD *houseColors[8] = { // Spain - new HouseColorHD(float3(0.26f, 1.0f, 1.0f), float3(0.0f, 1.0f, 0.22f), 0, float3(0.777f,-0.196, -0.0f), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.737255, 0.627451, 0.239216) ), + new HouseColorHD(float3(0.26, 1.0f, 0.0f), float3(0.0f, 1.0f, 0.22f), 0, float3(0.713,-0.621, 0.000000), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.6421568, 0.4784698, 0.4784698)), // Greece new HouseColorHD(float3(0.264706f, 1.0f, 0.0f), float3(0.0f, 1.0f, 0.22f), 0, float3(0.310000f,-0.211000, 0.005000), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.0980f, 0.3922, 0.9216)), // USSR new HouseColorHD(float3(0.264706f, 1.0f, 0.0f), float3(0.0f, 1.0f, 0.22f), 0, float3(0.689000f,0.382000, 0.000000), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.756863f, 0.0, 0.015686)), // England - new HouseColorHD(float3(0.26, 1.0f, 0.0f), float3(0.0f, 1.0f, 0.22f), 0, float3(0.911f,-0.381, 0.000000), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.184314, 0.67451, 0.141176)), + new HouseColorHD(float3(0.26f, 1.0f, 1.0f), float3(0.0f, 1.0f, 0.22f), 0, float3(0.777f,-0.196, -0.0f), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.737255, 0.627451, 0.239216)), // Ukraine new HouseColorHD(float3(0.26, 1.0f, 0.0f), float3(0.0f, 1.0f, 0.22f), 0, float3(0.726,0.119, 0.000000), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.772549, 0.380392, 0.07451)), // Germany - new HouseColorHD(float3(0.26, 1.0f, 0.0f), float3(0.0f, 1.0f, 0.22f), 0, float3(0.713,-0.621, 0.000000), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.6421568, 0.4784698, 0.4784698)), + new HouseColorHD(float3(0.26, 1.0f, 0.0f), float3(0.0f, 1.0f, 0.22f), 0, float3(0.911f,-0.381, 0.000000), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.184314, 0.67451, 0.141176)), // France new HouseColorHD(float3(0.26, 1.0f, 0.0f), float3(0.0f, 1.0f, 0.22f), 0, float3(0.119,-0.233, 0.000000), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.0f, 1.0f, 1.0f), float2(0.0f, 1.0f), float3(0.1151961, 0.6911765, 0.4031861)), // Turkey