diff --git a/README.md b/README.md index 02a1919..46f27db 100644 --- a/README.md +++ b/README.md @@ -150,11 +150,14 @@ https://discord.gg/t5jRGbj - [X Labs](https://github.com/XLabsProject) (especially [Snake](https://github.com/momo5502)) - [The Plutonium Project Team](https://plutonium.pw/) (especially [Rektinator](https://github.com/RektInator)) - [Nukem9 - LinkerMod](https://github.com/Nukem9/LinkerMod) & [detours](https://github.com/Nukem9/detours) +- [JTAG - NootNoot](https://twitter.com/imjtagmodz) - [ocornut - Dear ImGui](https://github.com/ocornut/imgui) - [CedricGuillemet - ImGuizmo](https://github.com/CedricGuillemet/ImGuizmo) - [nlohmann - fifo_map](https://github.com/nlohmann/fifo_map) - [David Gallardo - imgui_color_gradient](https://gist.github.com/galloscript/8a5d179e432e062550972afcd1ecf112) - [nem0 - ImGui CurveEditor](https://github.com/nem0/LumixEngine/blob/39e46c18a58111cc3c8c10a4d5ebbb614f19b1b8/external/imgui/imgui_user.inl#L505-L930) +- [zfedoran - ImGui Spinner](https://github.com/ocornut/imgui/issues/1901) +- [maluoi - tga writer](https://gist.github.com/maluoi/ade07688e741ab188841223b8ffeed22) - [Infinity Ward - OG. Radiant and Effects Framework](https://www.infinityward.com) - [id-Software - OG. Radiant](https://github.com/id-Software/Quake-III-Arena/tree/master/q3radiant) diff --git a/readme/licenses.txt b/readme/licenses.txt new file mode 100644 index 0000000..221f54d --- /dev/null +++ b/readme/licenses.txt @@ -0,0 +1,31 @@ +MIT License + +https://github.com/Nukem9/detours +Copyright (c) 2019 Nukem + +https://github.com/ocornut/imgui +Copyright (c) 2014-2022 Omar Cornut + +https://github.com/nlohmann/fifo_map +Copyright (c) 2015-2017 Niels Lohmann + +https://gist.github.com/maluoi/ade07688e741ab188841223b8ffeed22 +Copyright (c) 2019 Nick Klingensmith (@koujaku) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/src/components/loader.cpp b/src/components/loader.cpp index 8044155..5bfad68 100644 --- a/src/components/loader.cpp +++ b/src/components/loader.cpp @@ -22,6 +22,7 @@ namespace components loader::_register(new main_module()); loader::_register(new pmesh()); loader::_register(new process()); + loader::_register(new reflectionprobes()); loader::_register(new renderer()); loader::_register(new remote_net()); diff --git a/src/components/loader.hpp b/src/components/loader.hpp index abbc2af..145d5a4 100644 --- a/src/components/loader.hpp +++ b/src/components/loader.hpp @@ -38,5 +38,6 @@ namespace components #include "modules/main_module.hpp" #include "modules/pmesh.hpp" #include "modules/process.hpp" +#include "modules/reflectionprobes.hpp" #include "modules/renderer.hpp" #include "modules/remote_net.hpp" diff --git a/src/components/modules/d3dbsp.cpp b/src/components/modules/d3dbsp.cpp index d05e620..17b2ebc 100644 --- a/src/components/modules/d3dbsp.cpp +++ b/src/components/modules/d3dbsp.cpp @@ -163,6 +163,76 @@ namespace components return false; } +#pragma warning(push) +#pragma warning(disable: 4146) + void d3dbsp::Com_SaveLump(LumpType type, const void* newLump, unsigned int size) + { + const void* chunkData[100]; + + BspHeader new_header = {}; + new_header.ident = 1347633737; + new_header.version = 22; + new_header.chunkCount = 0; + + bool isNewChunk = true; + unsigned int offset = 8 * comBspGlob.header->chunkCount + 12; + + for (auto chunkIter = 0u; chunkIter < comBspGlob.header->chunkCount; ++chunkIter) + { + const BspChunk* chunk = &comBspGlob.header->chunks[chunkIter]; + if (chunk->type == type) + { + isNewChunk = false; + if (size) + { + new_header.chunks[new_header.chunkCount].type = type; + new_header.chunks[new_header.chunkCount].length = size; + chunkData[new_header.chunkCount++] = newLump; + } + } + else + { + new_header.chunks[new_header.chunkCount].type = chunk->type; + new_header.chunks[new_header.chunkCount].length = chunk->length; + chunkData[new_header.chunkCount++] = (char*)comBspGlob.header + offset; + } + + offset += (chunk->length + 3) & 0xFFFFFFFC; + } + if (isNewChunk && size) + { + new_header.chunks[new_header.chunkCount].type = type; + new_header.chunks[new_header.chunkCount].length = size; + chunkData[new_header.chunkCount++] = newLump; + } + + const auto h = game::FS_OpenFileOverwrite(comBspGlob.name); + if (h) + { + game::FS_Write(&new_header, 8 * new_header.chunkCount + 12, h); + for (auto chunkIter = 0u; chunkIter < new_header.chunkCount; ++chunkIter) + { + game::FS_Write(chunkData[chunkIter], new_header.chunks[chunkIter].length, h); + const unsigned int zeroCount = -new_header.chunks[chunkIter].length & 3; + + if (zeroCount) + { + unsigned int zero; + game::FS_Write(&zero, zeroCount, h); + } + + } + + game::FS_FCloseFile(h); + d3dbsp::radiant_load_bsp(d3dbsp::loaded_bsp_path.c_str(), true); + } + else + { + game::Com_Error("Failed to open file %s for writing", comBspGlob.name); + } + } +#pragma warning(pop) + const void* d3dbsp::Com_GetBspLump(LumpType type, unsigned int elemSize, unsigned int* count) { const void* result = nullptr; @@ -472,7 +542,7 @@ namespace components if (bytesRead != comBspGlob.fileSize) { - free(comBspGlob.header); + game::Z_Free(comBspGlob.header); game::printf_to_console("[ERR][BSP] bytesRead != comBspGlob.fileSize"); return false; } @@ -489,9 +559,8 @@ namespace components d3dbsp::dobj_clear_list(); comBspGlob.loadedLumpData = nullptr; - - // free(comBspGlob.header); - utils::hook::call(0x4AC2A0)(comBspGlob.header); + + game::Z_Free(comBspGlob.header); comBspGlob.header = nullptr; comBspGlob.name[0] = 0; @@ -715,6 +784,11 @@ namespace components process::pthis->set_callback([bsp_path] { d3dbsp::radiant_load_bsp(bsp_path.c_str(), true); + + if(dvars::bsp_gen_reflections_on_compile->current.enabled) + { + dvars::set_bool(dvars::r_reflectionprobe_generate, true); + } }); process::pthis->create_process(); @@ -798,6 +872,12 @@ namespace components /* flags */ game::dvar_flags::saved, /* desc */ "enable to load entities when loading a bsp (static_models only)"); + dvars::bsp_gen_reflections_on_compile = dvars::register_bool( + /* name */ "bsp_gen_reflections_on_compile", + /* default */ true, + /* flags */ game::dvar_flags::saved, + /* desc */ "automatically build reflections when compiling the bsp"); + dvars::r_draw_bsp = dvars::register_bool( /* name */ "r_draw_bsp", /* default */ false, diff --git a/src/components/modules/d3dbsp.hpp b/src/components/modules/d3dbsp.hpp index 0db7c76..dd50470 100644 --- a/src/components/modules/d3dbsp.hpp +++ b/src/components/modules/d3dbsp.hpp @@ -123,6 +123,10 @@ namespace components static bool Com_IsBspLoaded(); static std::uint32_t Com_GetBspVersion(); static char* Com_ValidateBspLumpData(LumpType type, unsigned int offset, unsigned int length, unsigned int elemSize, unsigned int* count); + + static void Com_SaveLump(LumpType type, const void* newLump, unsigned int size); + static void Com_SaveLump_t5(LumpType type, const void* newLump, unsigned int size); + static const void* Com_GetBspLump(LumpType type, unsigned int elemSize, unsigned int* count); static bool Com_GetBspLumpBool(LumpType type); static bool Com_LoadBsp(const char* filename); diff --git a/src/components/modules/gui.cpp b/src/components/modules/gui.cpp index c60e61b..03f4ebf 100644 --- a/src/components/modules/gui.cpp +++ b/src/components/modules/gui.cpp @@ -257,6 +257,11 @@ namespace components // main rendering loop (d3d9ex::d3d9device::EndScene()) void gui::render_loop() { + if(dvars::r_reflectionprobe_generate->current.enabled) + { + return; + } + exec::on_gui_execute(); /* - radiant draws multiple windows using d3d @@ -378,8 +383,47 @@ namespace components ShowWindow(con, SW_HIDE); } - // debug +#if 0 + if (game::s_world->reflectionProbes && game::s_world->reflectionProbes->reflectionImage + && game::s_world->reflectionProbes->reflectionImage->texture.data) + { + ImGui::Begin("Reflection Probes Debug", nullptr); + + ImGui::Image(game::s_world->reflectionProbes[0].reflectionImage->texture.data, ImVec2(300, 300)); + ImGui::SameLine(); + ImGui::Image(game::s_world->reflectionProbes[1].reflectionImage->texture.data, ImVec2(300, 300)); + + + if (reflectionprobes::imgui_cube_surfaces[0]) + ImGui::Image(reflectionprobes::imgui_cube_surfaces[0], ImVec2(300, 300)); + + + ImGui::SameLine(); + if (reflectionprobes::imgui_cube_surfaces[1]) + ImGui::Image(reflectionprobes::imgui_cube_surfaces[1], ImVec2(300, 300)); + + ImGui::SameLine(); + if (reflectionprobes::imgui_cube_surfaces[2]) + ImGui::Image(reflectionprobes::imgui_cube_surfaces[2], ImVec2(300, 300)); + + + if (reflectionprobes::imgui_cube_surfaces[3]) + ImGui::Image(reflectionprobes::imgui_cube_surfaces[3], ImVec2(300, 300)); + + ImGui::SameLine(); + if (reflectionprobes::imgui_cube_surfaces[4]) + ImGui::Image(reflectionprobes::imgui_cube_surfaces[4], ImVec2(300, 300)); + + ImGui::SameLine(); + if (reflectionprobes::imgui_cube_surfaces[5]) + ImGui::Image(reflectionprobes::imgui_cube_surfaces[5], ImVec2(300, 300)); + + ImGui::End(); + } +#endif + + // debug //game::GfxRenderTarget* targets = reinterpret_cast(0x174F4A8); //game::GfxRenderTarget* depth = &targets[game::R_RENDERTARGET_FLOAT_Z]; @@ -387,12 +431,12 @@ namespace components ImGui::Image(postSun->image->texture.data, ImVec2(game::dx->windows[ggui::e_gfxwindow::CCAMERAWND].width, game::dx->windows[ggui::e_gfxwindow::CCAMERAWND].height)); ImGui::End();*/ - //if(depth && depth->image && depth->image->texture.data) - //{ - // ImGui::Begin("Depthbuffer", nullptr); - // ImGui::Image(depth->image->texture.data, ImVec2(ggui::get_rtt_camerawnd()->scene_size_imgui.x, ggui::get_rtt_camerawnd()->scene_size_imgui.y)); - // ImGui::End(); - //} + /*if(depth && depth->image && depth->image->texture.data) + { + ImGui::Begin("Depthbuffer", nullptr); + ImGui::Image(depth->image->texture.data, ImVec2(300, 300)); + ImGui::End(); + }*/ // end the current context frame goto END_FRAME; diff --git a/src/components/modules/reflectionprobes.cpp b/src/components/modules/reflectionprobes.cpp new file mode 100644 index 0000000..5bc8ab9 --- /dev/null +++ b/src/components/modules/reflectionprobes.cpp @@ -0,0 +1,822 @@ +#include "std_include.hpp" + +namespace components +{ + std::uint8_t* reflectionprobes::cubemapshot_image_ptr[6]; + IDirect3DTexture9* reflectionprobes::imgui_cube_surfaces[6]; + + enum IMG_CUBE_COORD + { + CUBE_X0, + CUBE_X1, + CUBE_Y0, + CUBE_Y1, + }; + + int get_cubemapshot_res() + { + return 256; + } + + int get_pixel_border() + { + return 1; + } + + + + // no ext + void export_cubeshot_side_as_targa(const std::string& filename, uint8_t* dataBGRA) + { + if (dvars::fs_homepath = game::Dvar_FindVar("fs_homepath"); + dvars::fs_homepath) + { + std::string filePath = dvars::fs_homepath->current.string; + filePath += R"(\IW3xRadiant\reflection_probes\)"; + + std::filesystem::create_directories(filePath); + filePath += filename + ".tga"s; + + utils::tga_write( + filePath.c_str(), + get_cubemapshot_res(), + get_cubemapshot_res(), + dataBGRA, + 4, + 4); + } + } + + bool R_TakeCubeMapShot(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height, std::int32_t bytes_per_pixel, std::uint8_t* buffer) + { + IDirect3DSurface9* back_buffer = nullptr; + IDirect3DSurface9* surface = nullptr; + D3DSURFACE_DESC desc = {}; + D3DLOCKED_RECT locked_rect = {}; + const tagRECT source_rect{ x, y, width + x, height + y }; + + if (!SUCCEEDED(game::dx->windows[ggui::CCAMERAWND].swapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &back_buffer))) + { + AssertS("ERROR: cannot take screenshot: couldn't get back buffer surface\n"); + back_buffer->Release(); + return false; + } + + if (!SUCCEEDED(back_buffer->GetDesc(&desc))) + { + AssertS("ERROR: cannot take screenshot: couldn't get desc\n"); + back_buffer->Release(); + return false; + } + + if (!SUCCEEDED(game::dx->device->CreateOffscreenPlainSurface(desc.Width, desc.Height, desc.Format, D3DPOOL_SYSTEMMEM, &surface, nullptr))) + { + AssertS("ERROR: cannot take screenshot: couldn't create the off-screen surface\n"); + surface->Release(); + back_buffer->Release(); + return false; + } + + if (!SUCCEEDED(game::dx->device->GetRenderTargetData(back_buffer, surface))) + { + AssertS("ERROR: cannot take screenshot: GetRenderTargetData failed\n"); + surface->Release(); + back_buffer->Release(); + return false; + } + + if (!SUCCEEDED(surface->LockRect(&locked_rect, &source_rect, D3DLOCK_READONLY))) + { + surface->Release(); + back_buffer->Release(); + return false; + } + + if (auto bits = static_cast(locked_rect.pBits)) + { + if (bytes_per_pixel == 3) + { + for (auto i = 0; i < height; i++) + { + for (auto j = 0; j < width; j++) + { + buffer[0] = bits[0]; + buffer[1] = bits[1]; + buffer[2] = bits[2]; + + buffer += 3; + bits += 4; + } + } + } + else if (locked_rect.Pitch == (4 * width)) + { + memcpy(buffer, bits, 4 * height * width); + } + else + { + for (auto i = 0; i < height; i++) + { + + memcpy(buffer, bits, (4 * width)); + buffer += (4 * width); + bits += locked_rect.Pitch; + } + } + } + + surface->UnlockRect(); + surface->Release(); + back_buffer->Release(); + + return true; + } + + void Image_FlipVertically(std::uint8_t* pic, const int size) + { + for (auto s = 0; s < size; ++s) + { + const auto p = reinterpret_cast(&pic[4 * s]); + + for (auto t = 0; t < size / 2; ++t) + { + const auto np = &p[size * (size - 1) - size * t]; + const auto cache = p[size * t]; + + p[size * t] = *np; + *np = cache; + } + } + } + + void Image_FlipDiagonally(std::uint8_t* pic, const int size) + { + for (auto s = 1; s < size; ++s) + { + for (auto t = 0; t < s; ++t) + { + const int cache = *reinterpret_cast(&pic[4 * s + 4 * size * t]); + *reinterpret_cast(&pic[4 * s + 4 * size * t]) = *reinterpret_cast(&pic[4 * t + 4 * size * s]); + *reinterpret_cast(&pic[4 * t + 4 * size * s]) = cache; + } + } + } + + void Image_FlipHorizontally(std::uint8_t* pic, const int size) + { + for (auto t = 0; t < size; ++t) + { + const auto pa = reinterpret_cast(&pic[4 * size * t]); + const auto pb = reinterpret_cast(&pic[4 * size * t - 4 + 4 * size]); + + for (auto s = 0; s < size / 2; ++s) + { + const int cache = pa[s]; + pa[s] = pb[-s]; + pb[-s] = cache; + } + } + } + + void R_CubemapShotDownSample(std::uint8_t* pixels, const int base_res, const int down_sample_size) + { + float total[4]; + std::uint8_t* pixel; + + for (auto y = 0; y < down_sample_size; ++y) + { + for (auto x = 0; x < down_sample_size; ++x) + { + total[0] = 0.0f; + total[1] = 0.0f; + total[2] = 0.0f; + total[3] = 0.0f; + + for (auto suby = 0; suby < 4; ++suby) + { + for (auto subx = 0; subx < 4; ++subx) + { + pixel = &pixels[4 * (subx + base_res * (suby + 4 * y) + 4 * x)]; + + for (auto col_index = 0; col_index < 4; ++col_index) + { + total[col_index] = static_cast(pixel[col_index]) + total[col_index]; + } + + } + } + + pixel = &pixels[4 * (x + down_sample_size * y)]; + + for (auto i = 0; i < 4; ++i) + { + total[i] = total[i] / 16.0f; + pixel[i] = static_cast(total[i]); + } + } + } + } + + void Image_Blend1x1Faces(std::uint8_t* (*pixels)[15], const int mip_level) + { + for (auto color = 0; color < 4; ++color) + { + float value = 0.0f; + for (auto f = 0; f < 6; ++f) + { + value = (float)(&(*pixels)[15 * f])[mip_level][color] + value; + } + + value = value / 6.0f; + for (auto i = 0; i < 6; ++i) + { + (&(*pixels)[15 * i])[mip_level][color] = static_cast(value); + } + } + } + + std::uint8_t* Image_GetCubeCornerPixel(std::uint8_t* face_pixels, const int coordx, const int coordy, const int edge_size) + { + int x; + int y; + + if (coordx) + { + if (coordx != 1) + { + return nullptr; + } + + x = edge_size - 1; + } + else + { + x = 0; + } + + if (coordy == 2) + { + y = 0; + return &face_pixels[4 * (x + y * edge_size)]; + } + + if (coordy == 3) + { + y = edge_size - 1; + return &face_pixels[4 * (x + y * edge_size)]; + } + + return nullptr; + } + + std::uint8_t* Image_GetCubeFaceEdgePixel(std::uint8_t* face_pixels, const int pixel, const int edge_size, IMG_CUBE_COORD edge) + { + std::uint8_t* result; + + switch (edge) + { + case CUBE_X0: + result = &face_pixels[pixel * 4 * edge_size]; + break; + case CUBE_X1: + result = &face_pixels[4 * (edge_size + pixel * edge_size) - 4]; + break; + case CUBE_Y0: + result = &face_pixels[4 * pixel]; + break; + case CUBE_Y1: + result = &face_pixels[4 * (pixel + edge_size * (edge_size - 1))]; + break; + default: + result = nullptr; + break; + } + + return result; + } + + void Image_BlendCubeCorner(std::uint8_t* face_pixels0, std::uint8_t* face_pixels1, std::uint8_t* face_pixels2, const int edge_size, IMG_CUBE_COORD coord0x, IMG_CUBE_COORD coord0y, IMG_CUBE_COORD coord1x, IMG_CUBE_COORD coord1y, IMG_CUBE_COORD coord2x, IMG_CUBE_COORD coord2y) + { + const auto pixel0 = Image_GetCubeCornerPixel(face_pixels0, coord0x, coord0y, edge_size); + const auto pixel1 = Image_GetCubeCornerPixel(face_pixels1, coord1x, coord1y, edge_size); + const auto pixel2 = Image_GetCubeCornerPixel(face_pixels2, coord2x, coord2y, edge_size); + + for (auto color = 0; color < 4; ++color) + { + pixel0[color] = static_cast( (static_cast( (pixel2[color] + pixel1[color] + pixel0[color]) ) / 3.0f) ); + pixel1[color] = pixel0[color]; + pixel2[color] = pixel0[color]; + } + } + + void Image_BlendCubeFaceEdge(std::uint8_t* this_face_pixels, std::uint8_t* other_face_pixels, const int edge_size, IMG_CUBE_COORD this_edge, IMG_CUBE_COORD other_edge, bool flip) + { + for (auto i = 0; i < edge_size; ++i) + { + int iother = i; + + if (flip) + { + iother = edge_size - i - 1; + } + + const auto this_pixel = Image_GetCubeFaceEdgePixel(this_face_pixels, i, edge_size, this_edge); + const auto other_pixel = Image_GetCubeFaceEdgePixel(other_face_pixels, iother, edge_size, other_edge); + + for (auto color = 0; color < 4; ++color) + { + this_pixel[color] = (other_pixel[color] + this_pixel[color]) / 2; + other_pixel[color] = this_pixel[color]; + } + } + } + + void CubeMap_BlendFaceEdges(std::uint8_t* (*pixels)[15], const int mip_level, const int edge_size) + { + if (edge_size == 1) + { + Image_Blend1x1Faces(pixels, mip_level); + } + else + { + Image_BlendCubeCorner( + (*pixels)[mip_level], (*pixels)[mip_level + 60], (*pixels)[mip_level + 30], edge_size, + CUBE_X0, CUBE_Y0, CUBE_X1, CUBE_Y0, CUBE_X1, CUBE_Y1); + + Image_BlendCubeCorner( + (*pixels)[mip_level], (*pixels)[mip_level + 60], (*pixels)[mip_level + 45], edge_size, + CUBE_X0, CUBE_Y1, CUBE_X1, CUBE_Y1, CUBE_X1, CUBE_Y0); + + Image_BlendCubeCorner( + (*pixels)[mip_level], (*pixels)[mip_level + 75], (*pixels)[mip_level + 30], edge_size, + CUBE_X1, CUBE_Y0,CUBE_X0, CUBE_Y0, CUBE_X1, CUBE_Y0); + + Image_BlendCubeCorner( + (*pixels)[mip_level], (*pixels)[mip_level + 75], (*pixels)[mip_level + 45], edge_size, + CUBE_X1, CUBE_Y1, CUBE_X0, CUBE_Y1, CUBE_X1, CUBE_Y1); + + Image_BlendCubeCorner( + (*pixels)[mip_level + 15], (*pixels)[mip_level + 60], (*pixels)[mip_level + 30], edge_size, + CUBE_X1, CUBE_Y0, CUBE_X0, CUBE_Y0, CUBE_X0, CUBE_Y1); + + Image_BlendCubeCorner( + (*pixels)[mip_level + 15], (*pixels)[mip_level + 60], (*pixels)[mip_level + 45], edge_size, + CUBE_X1, CUBE_Y1, CUBE_X0, CUBE_Y1, CUBE_X0, CUBE_Y0); + + Image_BlendCubeCorner( + (*pixels)[mip_level + 15], (*pixels)[mip_level + 75], (*pixels)[mip_level + 30], edge_size, + CUBE_X0, CUBE_Y0, CUBE_X1, CUBE_Y0, CUBE_X0, CUBE_Y0); + + Image_BlendCubeCorner( + (*pixels)[mip_level + 15], (*pixels)[mip_level + 75], (*pixels)[mip_level + 45], edge_size, + CUBE_X0, CUBE_Y1, CUBE_X1, CUBE_Y1, CUBE_X0, CUBE_Y1); + + Image_BlendCubeFaceEdge((*pixels)[mip_level], (*pixels)[mip_level + 60], edge_size, CUBE_X0, CUBE_X1, false); + Image_BlendCubeFaceEdge((*pixels)[mip_level], (*pixels)[mip_level + 75], edge_size, CUBE_X1, CUBE_X0, false); + Image_BlendCubeFaceEdge((*pixels)[mip_level], (*pixels)[mip_level + 30], edge_size, CUBE_Y0, CUBE_X1, true); + Image_BlendCubeFaceEdge((*pixels)[mip_level], (*pixels)[mip_level + 45], edge_size, CUBE_Y1, CUBE_X1, false); + + Image_BlendCubeFaceEdge( + (*pixels)[mip_level + 15], (*pixels)[mip_level + 30], edge_size, + CUBE_Y0, CUBE_X0, false); + + Image_BlendCubeFaceEdge( + (*pixels)[mip_level + 15], (*pixels)[mip_level + 45], edge_size, + CUBE_Y1, CUBE_X0, true); + + Image_BlendCubeFaceEdge( + (*pixels)[mip_level + 15], (*pixels)[mip_level + 60], edge_size, + CUBE_X1, CUBE_X0, false); + + Image_BlendCubeFaceEdge( + (*pixels)[mip_level + 15], (*pixels)[mip_level + 75], edge_size, + CUBE_X0, CUBE_X1, false); + + Image_BlendCubeFaceEdge( + (*pixels)[mip_level + 45], (*pixels)[mip_level + 60], edge_size, + CUBE_Y0, CUBE_Y1, false); + + Image_BlendCubeFaceEdge((*pixels)[mip_level + 45], + (*pixels)[mip_level + 75], edge_size, + CUBE_Y1, CUBE_Y1, true); + + Image_BlendCubeFaceEdge( + (*pixels)[mip_level + 30], (*pixels)[mip_level + 60], edge_size, + CUBE_Y1, CUBE_Y0, false); + + Image_BlendCubeFaceEdge( + (*pixels)[mip_level + 30], (*pixels)[mip_level + 75], edge_size, + CUBE_Y0, CUBE_Y0, true); + } + } + + void R_CreateReflectionRawDataFromCubemapShot(game::DiskGfxReflectionProbe* probe_raw_data, const int down_sample_size) + { + for (auto f = 0; f < 6; f++) + { + R_CubemapShotDownSample(reflectionprobes::cubemapshot_image_ptr[f], get_cubemapshot_res(), down_sample_size); + Image_FlipVertically(reflectionprobes::cubemapshot_image_ptr[f], down_sample_size); + } + + Image_FlipDiagonally(reflectionprobes::cubemapshot_image_ptr[0], down_sample_size); + Image_FlipDiagonally(reflectionprobes::cubemapshot_image_ptr[1], down_sample_size); + + Image_FlipHorizontally(reflectionprobes::cubemapshot_image_ptr[1], down_sample_size); + + Image_FlipVertically(reflectionprobes::cubemapshot_image_ptr[1], down_sample_size); + Image_FlipVertically(reflectionprobes::cubemapshot_image_ptr[2], down_sample_size); + + Image_FlipHorizontally(reflectionprobes::cubemapshot_image_ptr[3], down_sample_size); + + Image_FlipDiagonally(reflectionprobes::cubemapshot_image_ptr[4], down_sample_size); + Image_FlipDiagonally(reflectionprobes::cubemapshot_image_ptr[5], down_sample_size); + + + std::uint8_t* pixels[6][15]; + const int mipmap_level_size = down_sample_size * 4 * down_sample_size; + auto raw_pixels = reinterpret_cast(probe_raw_data->pixels); + + for (auto img_index = 0; img_index < 6; ++img_index) + { + pixels[img_index][0] = raw_pixels; + raw_pixels += mipmap_level_size; + memcpy(pixels[img_index][0], reflectionprobes::cubemapshot_image_ptr[img_index], mipmap_level_size); + } + + + // # + // would normally generate mip maps here .. no need + + + // only blend edges of mip 0 + CubeMap_BlendFaceEdges(pixels, 0, down_sample_size); + } + + + bool R_EndCubemapShot(game::CubemapShot shot_index, int probe_num) + { + const int img_index = shot_index - 1; + const int size_in_bytes = 4 * get_cubemapshot_res() * get_cubemapshot_res(); + + reflectionprobes::cubemapshot_image_ptr[img_index] = static_cast(game::Z_VirtualAlloc(size_in_bytes)); + if(!R_TakeCubeMapShot(get_pixel_border(), get_pixel_border(), get_cubemapshot_res(), get_cubemapshot_res(), 4, reflectionprobes::cubemapshot_image_ptr[img_index])) + { + return false; + } + + if(dvars::r_reflectionprobe_export_tga->current.enabled) + { + export_cubeshot_side_as_targa( + "cubemap_shot_n" + std::to_string(probe_num) + "_" + std::to_string(shot_index), + reflectionprobes::cubemapshot_image_ptr[img_index]); + } + + Image_FlipVertically(reflectionprobes::cubemapshot_image_ptr[img_index], get_cubemapshot_res()); + + return true; + } + + void R_CalcCubeMapViewValues(game::refdef_s* refdef, game::CubemapShot shot, int shot_size) + { + refdef->width = shot_size + 2; + refdef->height = shot_size + 2; + refdef->tanHalfFovX = (static_cast(shot_size) + 2) / static_cast(shot_size); + refdef->tanHalfFovY = refdef->tanHalfFovX; + refdef->zNear = 4.0f; // was 0 + refdef->x = 0; + refdef->y = 0; + + switch (shot) + { + case game::CUBEMAPSHOT_RIGHT: + refdef->viewaxis[0][0] = 1.0f; + refdef->viewaxis[0][1] = 0.0f; + refdef->viewaxis[0][2] = 0.0f; + refdef->viewaxis[1][0] = 0.0f; + refdef->viewaxis[1][1] = 1.0f; + refdef->viewaxis[1][2] = 0.0f; + refdef->viewaxis[2][0] = 0.0f; + refdef->viewaxis[2][1] = 0.0f; + refdef->viewaxis[2][2] = 1.0f; + break; + + case game::CUBEMAPSHOT_LEFT: + refdef->viewaxis[0][0] = -1.0f; + refdef->viewaxis[0][1] = 0.0f; + refdef->viewaxis[0][2] = 0.0f; + refdef->viewaxis[1][0] = 0.0f; + refdef->viewaxis[1][1] = -1.0f; + refdef->viewaxis[1][2] = 0.0f; + refdef->viewaxis[2][0] = 0.0f; + refdef->viewaxis[2][1] = 0.0f; + refdef->viewaxis[2][2] = 1.0f; + break; + + case game::CUBEMAPSHOT_FRONT: + refdef->viewaxis[0][0] = 0.0f; + refdef->viewaxis[0][1] = -1.0f; + refdef->viewaxis[0][2] = 0.0f; + refdef->viewaxis[1][0] = 1.0f; + refdef->viewaxis[1][1] = 0.0f; + refdef->viewaxis[1][2] = 0.0f; + refdef->viewaxis[2][0] = 0.0f; + refdef->viewaxis[2][1] = 0.0f; + refdef->viewaxis[2][2] = 1.0f; + break; + + case game::CUBEMAPSHOT_UP: + refdef->viewaxis[0][0] = 0.0f; + refdef->viewaxis[0][1] = 0.0f; + refdef->viewaxis[0][2] = 1.0f; + refdef->viewaxis[1][1] = 1.0f; + refdef->viewaxis[1][0] = 0.0f; + refdef->viewaxis[1][2] = 0.0f; + refdef->viewaxis[2][0] = -1.0f; + refdef->viewaxis[2][1] = 0.0f; + refdef->viewaxis[2][2] = 0.0f; + break; + case game::CUBEMAPSHOT_DOWN: + refdef->viewaxis[0][0] = 0.0f; + refdef->viewaxis[0][1] = 0.0f; + refdef->viewaxis[0][2] = -1.0f; + refdef->viewaxis[1][0] = 0.0f; + refdef->viewaxis[1][1] = 1.0f; + refdef->viewaxis[1][2] = 0.0f; + refdef->viewaxis[2][0] = 1.0f; + refdef->viewaxis[2][1] = 0.0f; + refdef->viewaxis[2][2] = 0.0f; + break; + + default: + refdef->viewaxis[0][0] = 0.0f; + refdef->viewaxis[0][1] = 1.0f; + refdef->viewaxis[0][2] = 0.0f; + refdef->viewaxis[1][0] = -1.0f; + refdef->viewaxis[1][1] = 0.0f; + refdef->viewaxis[1][2] = 0.0f; + refdef->viewaxis[2][0] = 0.0f; + refdef->viewaxis[2][1] = 0.0f; + refdef->viewaxis[2][2] = 1.0f; + break; + } + } + + + // not in use + void R_CubemapShotSetInitialState() + { + const game::vec4_t clear_color = { 1.0f, 1.0f, 1.0f, 1.0f }; + game::R_SetupRendertarget(game::gfxCmdBufSourceState, game::R_RENDERTARGET_FRAME_BUFFER); + game::R_SetRenderTarget(game::gfxCmdBufSourceState, game::gfxCmdBufState, game::R_RENDERTARGET_FRAME_BUFFER); + game::R_ClearScreen(game::gfxCmdBufState->prim.device, 7, clear_color, 1.0f, false, nullptr); + } + + bool R_GenerateReflectionRawData(game::DiskGfxReflectionProbe* probe, int probe_num) + { + game::refdef_s refdef = {}; + memset(&refdef, 0, sizeof(refdef)); + + refdef.vieworg[0] = probe->origin[0]; + refdef.vieworg[1] = probe->origin[1]; + refdef.vieworg[2] = probe->origin[2]; + refdef.localClientNum = 0; + refdef.time = 0; + refdef.useScissorViewport = false; + refdef.blurRadius = 0.0f; + memcpy(&refdef.primaryLights, &d3dbsp::scene_lights, sizeof(d3dbsp::scene_lights)); + + // final cubemap resolution + const int down_sample_res = get_cubemapshot_res() / 4; // 64 for 256 size + + // # + // resize window to cubemap size + const auto camgui = GET_GUI(ggui::camera_dialog); + + const auto saved_width = static_cast(camgui->rtt_get_size().x); //game::dx->windows[ggui::CCAMERAWND].width; + const auto saved_height = static_cast(camgui->rtt_get_size().y); //game::dx->windows[ggui::CCAMERAWND].height; + + SetWindowPos( + cmainframe::activewnd->m_pCamWnd->GetWindow(), + HWND_BOTTOM, + static_cast(camgui->rtt_get_position().x), + static_cast(camgui->rtt_get_position().y), + get_cubemapshot_res() + get_pixel_border(), //+ 2, + get_cubemapshot_res() + get_pixel_border(), //+ 2, + SWP_NOZORDER); + + game::dx->targetWindowIndex = -1; // needed or R_SetupRendertarget does not set the resolution + utils::hook::call(0x501A70)(cmainframe::activewnd->m_pCamWnd->GetWindow()); // R_SetupRendertarget + + + // # + // take shots + + for (int i = game::CUBEMAPSHOT_RIGHT; i < game::CUBEMAPSHOT_COUNT; ++i) + { + game::R_BeginFrame(); + + game::get_frontenddata()->cmds = &game::get_cmdlist()->cmds[game::get_cmdlist()->usedTotal]; + game::R_ClearScene(0); + + R_CalcCubeMapViewValues(&refdef, (game::CubemapShot)i, get_cubemapshot_res()); + game::R_SetLodOrigin(&refdef); + + d3dbsp::add_entities_to_scene(); + + // # + // fx (will only capture them if they are played back) + + const auto system = fx_system::FX_GetSystem(0); + fx_system::FX_SetupCamera(&system->camera, refdef.vieworg, refdef.viewaxis, refdef.tanHalfFovX, refdef.tanHalfFovY, game::Dvar_FindVar("r_zfar")->current.value); + + fx_system::FxCmd cmd = {}; + FX_FillUpdateCmd(0, &cmd); + Sys_DoWorkerCmd(fx_system::WRKCMD_UPDATE_FX_NON_DEPENDENT, &cmd); + Sys_DoWorkerCmd(fx_system::WRKCMD_UPDATE_FX_SPOT_LIGHT, &cmd); + Sys_DoWorkerCmd(fx_system::WRKCMD_UPDATE_FX_REMAINING, &cmd); + + // ---- + + game::CL_RenderScene(&refdef); + game::R_EndFrame(); + game::R_IssueRenderCommands(-1); + + // # for imgui : save probe sides + //renderer::copy_scene_to_texture(ggui::CCAMERAWND, reflectionprobes::imgui_cube_surfaces[i-1]); + + if(!R_EndCubemapShot((game::CubemapShot)i, probe_num)) + { + // # + // restore orig resolution + + SetWindowPos( + cmainframe::activewnd->m_pCamWnd->GetWindow(), + HWND_BOTTOM, + static_cast(camgui->rtt_get_position().x), + static_cast(camgui->rtt_get_position().y), + saved_width, + saved_height, + SWP_NOZORDER); + + game::dx->targetWindowIndex = -1; // needed or R_SetupRendertarget does not set the resolution + utils::hook::call(0x501A70)(cmainframe::activewnd->m_pCamWnd->GetWindow()); // R_SetupRendertarget + + return false; + } + } + + R_CreateReflectionRawDataFromCubemapShot(probe, down_sample_res); + + +#if 0 // save screenshots after rotations + for (auto face = 0; face < 6; face++) + { + export_cubeshot_side_as_targa( + "cubemap_shot_post_rot" + std::to_string(face), + reflectionprobes::cubemapshot_image_ptr[img_index]); + } +#endif + + + // # + // restore orig resolution + + SetWindowPos( + cmainframe::activewnd->m_pCamWnd->GetWindow(), + HWND_BOTTOM, + static_cast(camgui->rtt_get_position().x), + static_cast(camgui->rtt_get_position().y), + saved_width, + saved_height, + SWP_NOZORDER); + + game::dx->targetWindowIndex = -1; // needed or R_SetupRendertarget does not set the resolution + utils::hook::call(0x501A70)(cmainframe::activewnd->m_pCamWnd->GetWindow()); // R_SetupRendertarget + + return true; + } + + bool R_GenerateReflectionRawDataAll(game::DiskGfxReflectionProbe* probe_raw_data, unsigned int probe_count, bool* generate_probe) + { + for (auto index = 0u; index < probe_count; ++index) + { + if (generate_probe[index]) + { + if(!R_GenerateReflectionRawData(&probe_raw_data[index], index)) + { + return false; + } + } + } + + return true; + } + + void reflectionprobes::generate_reflections(game::GfxReflectionProbe* probes, const unsigned int probe_count) + { + bool generate_probe[256]; + + unsigned int lump_probe_count; + const auto probe_raw_lump_data = static_cast(d3dbsp::Com_GetBspLump(d3dbsp::LUMP_REFLECTION_PROBES, sizeof(game::DiskGfxReflectionProbe), &lump_probe_count)); + + if (probe_count != lump_probe_count) + { + AssertS("reflection probe count does not match bsp's"); + return; + } + + if (probe_count) + { + const unsigned int lump_size = sizeof(game::DiskGfxReflectionProbe) * probe_count; + const auto probe_raw_generated_data = reinterpret_cast(game::Z_Malloc(lump_size)); + + for (auto probe_index = 0u; probe_index < lump_probe_count; ++probe_index) + { + memcpy(probe_raw_generated_data[probe_index].colorCorrectionFilename, probe_raw_lump_data[probe_index].colorCorrectionFilename, sizeof(probe_raw_generated_data[probe_index].colorCorrectionFilename)); + probe_raw_generated_data[probe_index].origin[0] = probes[probe_index].origin[0]; + probe_raw_generated_data[probe_index].origin[1] = probes[probe_index].origin[1]; + probe_raw_generated_data[probe_index].origin[2] = probes[probe_index].origin[2]; + + // always generate all probes + generate_probe[probe_index] = true; + } + + if(R_GenerateReflectionRawDataAll(probe_raw_generated_data, lump_probe_count, generate_probe)) + { + d3dbsp::Com_SaveLump(d3dbsp::LUMP_REFLECTION_PROBES, probe_raw_generated_data, lump_size); + game::printf_to_console("[Reflections] Reflection probe lump inside bsp written successfully."); + game::R_GenerateReflectionImages(game::rgp->world->reflectionProbes, probe_raw_generated_data, lump_probe_count, 0); + } + + game::Z_Free(probe_raw_generated_data); + } + else + { + d3dbsp::Com_SaveLump(d3dbsp::LUMP_REFLECTION_PROBES, nullptr, 0); + } + } + + // call after R_IssueRenderCommands + void reflectionprobes::generate_reflections_for_bsp() + { + if(game::rgp->world) + { + game::printf_to_console("[Reflections] Generating reflections ..."); + reflectionprobes::generate_reflections(game::rgp->world->reflectionProbes + 1, game::rgp->world->reflectionProbeCount - 1); + } + } + + void check_for_reflection_generation() + { + if (dvars::r_reflectionprobe_generate->current.enabled) + { + if(!dvars::r_draw_bsp->current.enabled) + { + game::printf_to_console("[Reflections] Turning on bsp view ..."); + command::execute("toggle_bsp_radiant"); + } + + reflectionprobes::generate_reflections_for_bsp(); + dvars::set_bool(dvars::r_reflectionprobe_generate, false); + } + } + + void __declspec(naked) check_for_reflection_generation_stub() + { + const static uint32_t func_addr = 0x4FD910; // R_SortMaterials + const static uint32_t retn_addr = 0x403070; + __asm + { + pushad; + call check_for_reflection_generation; + popad; + + call func_addr; + jmp retn_addr; + } + } + + void reflectionprobes::register_dvars() + { + dvars::r_reflectionprobe_generate = dvars::register_bool( + /* name */ "r_reflectionprobe_generate", + /* default */ false, + /* flags */ game::dvar_flags::none, + /* desc */ "generate all reflection probes for the loaded d3dbsp"); + + dvars::r_reflectionprobe_export_tga = dvars::register_bool( + /* name */ "r_reflectionprobe_export_tga", + /* default */ false, + /* flags */ game::dvar_flags::none, + /* desc */ "export all reflection probes as tga files when building reflections (bin/IW3xRadiant/reflection_probes)"); + } + + reflectionprobes::reflectionprobes() + { + // hk 'R_SortMaterials' call after 'R_IssueRenderCommands' in 'CCamWnd::OnPaint' + utils::hook(0x40306B, check_for_reflection_generation_stub, HOOK_JUMP).install()->quick(); + } + + reflectionprobes::~reflectionprobes() + { } +} diff --git a/src/components/modules/reflectionprobes.hpp b/src/components/modules/reflectionprobes.hpp new file mode 100644 index 0000000..d842d13 --- /dev/null +++ b/src/components/modules/reflectionprobes.hpp @@ -0,0 +1,22 @@ +#pragma once + +namespace components +{ + class reflectionprobes : public component + { + + public: + reflectionprobes(); + ~reflectionprobes(); + const char* get_name() override { return "reflectionprobes"; }; + + static std::uint8_t* cubemapshot_image_ptr[6]; + static IDirect3DTexture9* imgui_cube_surfaces[6]; + + static void generate_reflections_for_bsp(); + static void register_dvars(); + + private: + static void generate_reflections(game::GfxReflectionProbe* probes, const unsigned int probe_count); + }; +} diff --git a/src/components/modules/renderer.cpp b/src/components/modules/renderer.cpp index df3ac62..b2e3366 100644 --- a/src/components/modules/renderer.cpp +++ b/src/components/modules/renderer.cpp @@ -1345,7 +1345,8 @@ namespace components if (const auto draw_water = game::Dvar_FindVar("r_drawWater"); draw_water && draw_water->current.enabled) { - if (utils::string_equals(state->material->techniqueSet->name, "wc_water")) + if (state && state->material && state->material->techniqueSet && + utils::string_equals(state->material->techniqueSet->name, "wc_water")) { if (const auto tech = Material_RegisterTechnique("water_l_sun", 1); tech) @@ -1701,7 +1702,7 @@ namespace components { // render emissive surfs (effects) renderer::RB_Draw3D(); - + // post effects logic (filmtweaks) camera_postfx(); } @@ -1887,8 +1888,7 @@ namespace components } } - void __declspec(naked) - RB_ExecuteRenderCommandsLoop_stub() + void __declspec(naked) RB_ExecuteRenderCommandsLoop_stub() { const static uint32_t retn_addr = 0x535B1F; __asm diff --git a/src/fx_system/fx_update.hpp b/src/fx_system/fx_update.hpp index ee9d43e..10b8a9d 100644 --- a/src/fx_system/fx_update.hpp +++ b/src/fx_system/fx_update.hpp @@ -60,6 +60,7 @@ namespace fx_system void FX_SetNextUpdateTime(int localClientNum, int time); #ifndef FXEDITOR + void FX_SetupCamera(FxCamera* camera, const float* vieworg, const float(*viewaxis)[3], const float tanHalfFovX, const float tanHalfFovY, const float zfar); void FX_SetupCamera_Radiant(); #endif diff --git a/src/game/dvars.cpp b/src/game/dvars.cpp index 311746d..22779bf 100644 --- a/src/game/dvars.cpp +++ b/src/game/dvars.cpp @@ -55,6 +55,7 @@ namespace dvars game::dvar_s* grid_draw_edge_coordinates = nullptr; game::dvar_s* bsp_load_entities = nullptr; + game::dvar_s* bsp_gen_reflections_on_compile = nullptr; game::dvar_s* r_draw_bsp = nullptr; game::dvar_s* r_draw_bsp_overwrite_sunlight = nullptr; @@ -75,6 +76,9 @@ namespace dvars game::dvar_s* r_sunpreview_shadow_dist = nullptr; game::dvar_s* r_sunpreview_shadow_enable = nullptr; + game::dvar_s* r_reflectionprobe_generate = nullptr; + game::dvar_s* r_reflectionprobe_export_tga = nullptr; + game::dvar_s* guizmo_enable = nullptr; game::dvar_s* guizmo_snapping = nullptr; game::dvar_s* guizmo_brush_mode = nullptr; @@ -196,11 +200,12 @@ namespace dvars { game::printf_to_console("[Dvars]: register_addon_dvars() start ...\n"); + components::d3dbsp::register_dvars(); + components::gameview::register_dvars(); components::gui::register_dvars(); + components::reflectionprobes::register_dvars(); components::remote_net::register_dvars(); components::renderer::register_dvars(); - components::gameview::register_dvars(); - components::d3dbsp::register_dvars(); cmainframe::register_dvars(); cxywnd::register_dvars(); diff --git a/src/game/dvars.hpp b/src/game/dvars.hpp index a42ba98..684dd88 100644 --- a/src/game/dvars.hpp +++ b/src/game/dvars.hpp @@ -55,6 +55,7 @@ namespace dvars extern game::dvar_s* grid_draw_edge_coordinates; extern game::dvar_s* bsp_load_entities; + extern game::dvar_s* bsp_gen_reflections_on_compile; extern game::dvar_s* r_draw_bsp; extern game::dvar_s* r_draw_bsp_overwrite_sunlight; @@ -74,7 +75,10 @@ namespace dvars extern game::dvar_s* r_sunpreview_shadow_dist; extern game::dvar_s* r_sunpreview_shadow_enable; - + + extern game::dvar_s* r_reflectionprobe_generate; + extern game::dvar_s* r_reflectionprobe_export_tga; + extern game::dvar_s* guizmo_enable; extern game::dvar_s* guizmo_snapping; extern game::dvar_s* guizmo_brush_mode; diff --git a/src/game/functions.cpp b/src/game/functions.cpp index 50099e3..236934a 100644 --- a/src/game/functions.cpp +++ b/src/game/functions.cpp @@ -112,6 +112,12 @@ namespace game return out; } + game::GfxCmdArray* get_cmdlist() + { + const auto out = reinterpret_cast(*game::s_cmdList_ptr); + return out; + } + game::entity_s* g_world_entity() { const auto ent = reinterpret_cast(*game::worldEntity_ptr); @@ -1252,6 +1258,16 @@ namespace game } } + int FS_OpenFileOverwrite(const char* path /*esi*/) + { + const static uint32_t func_addr = 0x4A05D0; + __asm + { + mov esi, path; + call func_addr; + } + } + game::GfxImage* Image_FindExisting(const char* name) { const static uint32_t Image_FindExisting_func = 0x513200; diff --git a/src/game/functions.hpp b/src/game/functions.hpp index 7480d41..f423c7b 100644 --- a/src/game/functions.hpp +++ b/src/game/functions.hpp @@ -83,6 +83,7 @@ namespace game static DWORD* frontEndDataOut_ptr = (DWORD*)(0x73D480); // frontEndDataOut pointer static DWORD* backEndDataOut_ptr = (DWORD*)(0x174F970); // backendEndDataOut pointer + static DWORD* s_cmdList_ptr = (DWORD*)(0x73D4A0); static DWORD* active_brushes_ptr = (DWORD*)(0x23F189C); static DWORD* active_brushes_next_ptr = (DWORD*)(0x23F18A0); static DWORD* currSelectedBrushes = (DWORD*)(0x23F1864); // (selected_brushes array pointer) @@ -146,6 +147,7 @@ namespace game extern game::GfxBackEndData* get_backenddata(); extern game::GfxBackEndData* get_frontenddata(); + extern game::GfxCmdArray* get_cmdlist(); extern game::entity_s* g_world_entity(); extern game::selbrush_def_t* g_active_brushes(); @@ -277,6 +279,10 @@ namespace game inline auto R_CmdBufSet3D = reinterpret_cast(0x53CFB0); inline auto R_SetGameTime = reinterpret_cast(0x55A4A0); inline auto R_SortWorldSurfaces = reinterpret_cast(0x52E9F0); + inline auto R_ClearScene = reinterpret_cast(0x505770); + inline auto R_SetLodOrigin = reinterpret_cast(0x505F90); + inline auto CL_RenderScene = reinterpret_cast(0x506030); + inline auto R_GenerateReflectionImages = reinterpret_cast(0x550F00); // sampler_index = the index used in shader_vars.h inline auto R_SetSampler = reinterpret_cast(0x538D70); @@ -306,7 +312,9 @@ namespace game inline auto DX_ResetDevice = reinterpret_cast(0x5015F0); inline auto Hunk_Alloc = reinterpret_cast(0x5104E0); inline auto Z_Malloc = reinterpret_cast(0x4AC330); - + inline auto Z_Free = reinterpret_cast(0x4AC2A0); + inline auto Z_VirtualAlloc = reinterpret_cast(0x4AC380); + // * // * --------------------- dvars ------------------------------ @@ -366,6 +374,8 @@ namespace game const char** FS_ListFilteredFilesWrapper(const char* path /*edx*/, const char* null /*esi*/, int* file_count); std::uint32_t FS_HashFileName(const char* fname, int hash_size); void* Hunk_FindDataForFileInternal(int hash /*eax*/, int data_type /*ebx*/, const char* name /*edi*/); + int FS_OpenFileOverwrite(const char* path /*esi*/); + inline auto FS_Write = reinterpret_cast(0x49FF10); game::GfxImage* Image_FindExisting(const char* name); game::GfxImage* Image_RegisterHandle(const char* name); diff --git a/src/game/structs.hpp b/src/game/structs.hpp index c8c0142..14e4b95 100644 --- a/src/game/structs.hpp +++ b/src/game/structs.hpp @@ -1960,6 +1960,25 @@ namespace game GfxImage* reflectionImage; }; + struct DiskGfxReflectionProbe + { + float origin[3]; + char colorCorrectionFilename[64]; + char pixels[131064]; + }; + + enum CubemapShot + { + CUBEMAPSHOT_NONE = 0x0, + CUBEMAPSHOT_RIGHT = 0x1, + CUBEMAPSHOT_LEFT = 0x2, + CUBEMAPSHOT_BACK = 0x3, + CUBEMAPSHOT_FRONT = 0x4, + CUBEMAPSHOT_UP = 0x5, + CUBEMAPSHOT_DOWN = 0x6, + CUBEMAPSHOT_COUNT = 0x7, + }; + struct cplane_s; struct dplane_t @@ -3244,7 +3263,7 @@ namespace game int viewInfoIndex; int viewInfoCount; GfxViewInfo* viewInfo; - char execState_cmd[4]; + const void* cmds; GfxLight sunLight[1]; int hasApproxSunDirChanged; int primDrawSurfPos; diff --git a/src/ggui/about.cpp b/src/ggui/about.cpp index d010f5a..918676b 100644 --- a/src/ggui/about.cpp +++ b/src/ggui/about.cpp @@ -1,13 +1,17 @@ #include "std_include.hpp" +#define CENTER_URL(text, link) \ + ImGui::SetCursorForCenteredText((text)); \ + ImGui::TextURL((text), (link)); + namespace ggui { void about_dialog::gui() { - ImGui::SetNextWindowSize(game::glob::gh_update_avail ? ImVec2(480.0f, 740.0f) : ImVec2(400.0f, 490.0f)); + ImGui::SetNextWindowSize(game::glob::gh_update_avail ? ImVec2(480.0f, 810.0f) : ImVec2(400.0f, 560.0f)); ImGui::SetNextWindowPos(ggui::get_initial_window_pos(), ImGuiCond_FirstUseEver); - if (ImGui::Begin("About##window", this->get_p_open(), ImGuiWindowFlags_NoCollapse /*| ImGuiWindowFlags_NoResize*/ | ImGuiWindowFlags_NoDocking)) + if (ImGui::Begin("About##window", this->get_p_open(), ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoDocking)) { SPACING(0.0f, 8.0f); @@ -59,21 +63,10 @@ namespace ggui SPACING(0.0f, 8.0f); - const char* github_repo_str = "Github Repository"; - ImGui::SetCursorForCenteredText(github_repo_str); - ImGui::TextURL(github_repo_str, "https://github.com/xoxor4d/iw3xo-radiant"); - - const char* github_project_str = "Github Project Page"; - ImGui::SetCursorForCenteredText(github_project_str); - ImGui::TextURL(github_project_str, "https://xoxor4d.github.io/projects/iw3xo-radiant/"); - - const char* latest_release_str = "Latest build"; - ImGui::SetCursorForCenteredText(latest_release_str); - ImGui::TextURL(latest_release_str, "https://github.com/xoxor4d/iw3xo-radiant/releases"); - - const char* discord_invite_str = "Discord"; - ImGui::SetCursorForCenteredText(discord_invite_str); - ImGui::TextURL(discord_invite_str, "https://discord.gg/t5jRGbj"); + CENTER_URL("Github Repository", "https://github.com/xoxor4d/iw3xo-radiant"); + CENTER_URL("Github Project Page", "https://xoxor4d.github.io/projects/iw3xo-radiant/"); + CENTER_URL("Latest build", "https://github.com/xoxor4d/iw3xo-radiant/releases"); + CENTER_URL("Discord", "https://discord.gg/t5jRGbj"); SPACING(0.0f, 8.0f); ImGui::Separator(); @@ -87,41 +80,19 @@ namespace ggui SPACING(0.0f, 8.0f); - const char* cred01_str = "The X-Labs Team (especially Snake)"; - ImGui::SetCursorForCenteredText(cred01_str); - ImGui::TextURL(cred01_str, "https://github.com/XLabsProject/"); - - const char* cred02_str = "The Plutonium Project Team (especially Rektinator)"; - ImGui::SetCursorForCenteredText(cred02_str); - ImGui::TextURL(cred02_str, "https://plutonium.pw/"); - - const char* cred03_str = "Nukem (LinkerMod / Detours)"; - ImGui::SetCursorForCenteredText(cred03_str); - ImGui::TextURL(cred03_str, "https://github.com/Nukem9"); - - const char* cred04_str = "ocornut (Dear ImGui)"; - ImGui::SetCursorForCenteredText(cred04_str); - ImGui::TextURL(cred04_str, "https://github.com/ocornut/imgui"); - - const char* cred05_str = "CedricGuillemet (ImGuizmo)"; - ImGui::SetCursorForCenteredText(cred05_str); - ImGui::TextURL(cred05_str, "https://github.com/CedricGuillemet/ImGuizmo"); - - const char* cred06_str = "nlohmann (fifo_map)"; - ImGui::SetCursorForCenteredText(cred06_str); - ImGui::TextURL(cred06_str, "https://github.com/nlohmann/fifo_map"); - - const char* cred07_str = "David Gallardo (imgui_color_gradient)"; - ImGui::SetCursorForCenteredText(cred07_str); - ImGui::TextURL(cred07_str, "https://gist.github.com/galloscript/8a5d179e432e062550972afcd1ecf112"); - - const char* cred08_str = "nem0 (CurveEditor)"; - ImGui::SetCursorForCenteredText(cred08_str); - ImGui::TextURL(cred08_str, "https://github.com/nem0/LumixEngine/blob/39e46c18a58111cc3c8c10a4d5ebbb614f19b1b8/external/imgui/imgui_user.inl#L505-L930"); - - const char* cred09_str = "Infinity Ward (OG. Radiant and Effects Framework)"; - ImGui::SetCursorForCenteredText(cred09_str); - ImGui::TextURL(cred09_str, "https://www.infinityward.com/"); + CENTER_URL("The X-Labs Team (especially Snake)", "https://github.com/XLabsProject/"); + CENTER_URL("The Plutonium Project Team (especially Rektinator)", "https://plutonium.pw/"); + CENTER_URL("Nukem (LinkerMod / Detours)", "https://github.com/Nukem9"); + CENTER_URL("JTAG (NootNoot)", "https://twitter.com/imjtagmodz"); + CENTER_URL("ocornut (Dear ImGui)", "https://github.com/ocornut/imgui"); + CENTER_URL("CedricGuillemet (ImGuizmo)", "https://github.com/CedricGuillemet/ImGuizmo"); + CENTER_URL("nlohmann (fifo_map)", "https://github.com/nlohmann/fifo_map"); + CENTER_URL("David Gallardo (imgui_color_gradient)", "https://gist.github.com/galloscript/8a5d179e432e062550972afcd1ecf112"); + CENTER_URL("nem0 (CurveEditor)", "https://github.com/nem0/LumixEngine/blob/39e46c18a58111cc3c8c10a4d5ebbb614f19b1b8/external/imgui/imgui_user.inl#L505-L930"); + CENTER_URL("zfedoran (ImGui Spinner)", "https://github.com/ocornut/imgui/issues/1901"); + CENTER_URL("maluoi (tga writer)", "https://gist.github.com/maluoi/ade07688e741ab188841223b8ffeed22"); + CENTER_URL("Infinity Ward (OG. Radiant and Effects Framework)", "https://www.infinityward.com/"); + CENTER_URL("id-Software (OG. OG. Radiant)", "https://github.com/id-Software/Quake-III-Arena/tree/master/q3radiant"); SPACING(0.0f, 8.0f); diff --git a/src/ggui/camera_settings.cpp b/src/ggui/camera_settings.cpp index b82d3ab..79abbc5 100644 --- a/src/ggui/camera_settings.cpp +++ b/src/ggui/camera_settings.cpp @@ -215,6 +215,16 @@ namespace ggui ImGui::EndDisabled(); } + // ----------------- + ImGui::title_with_seperator("Reflections", true, 0, 2, 6.0f); + + ImGui::Checkbox("Automatically compile reflections when building bsp", &dvars::bsp_gen_reflections_on_compile->current.enabled); TT(dvars::bsp_gen_reflections_on_compile->description); + + if (ImGui::Button("Generate Reflections", ImVec2(-style.FramePadding.x, ImGui::GetFrameHeight()))) + { + dvars::set_bool(dvars::r_reflectionprobe_generate, true); + } TT("Probes within the loaded bsp will be used to take screenshots ..\nso make sure your bsp is up-to-date."); + // ----------------- ImGui::title_with_seperator("Compiling", true, 0, 2, 6.0f); @@ -243,7 +253,7 @@ namespace ggui void camera_settings_dialog::gui() { const auto MIN_WINDOW_SIZE = ImVec2(400.0f, 220.0f); - const auto INITIAL_WINDOW_SIZE = ImVec2(400.0f, 700.0f); + const auto INITIAL_WINDOW_SIZE = ImVec2(400.0f, 720.0f); auto initial_window_pos = ggui::get_initial_window_pos(); diff --git a/src/ggui/help.cpp b/src/ggui/help.cpp index 299a181..5ae92cb 100644 --- a/src/ggui/help.cpp +++ b/src/ggui/help.cpp @@ -28,6 +28,7 @@ namespace ggui if (ImGui::TreeNodeEx("d3dbsp", ImGuiTreeNodeFlags_DefaultOpen)) { BULLET_LINK("Loading and Compiling", "https://xoxor4d.github.io/tutorials/iw3xradiant-d3dbsp/"); + BULLET_LINK("Generating Reflections", "https://xoxor4d.github.io/tutorials/iw3xradiant-d3dbsp-reflections/"); ImGui::TreePop(); } diff --git a/src/ggui/preferences.cpp b/src/ggui/preferences.cpp index 68a64b8..6ac5247 100644 --- a/src/ggui/preferences.cpp +++ b/src/ggui/preferences.cpp @@ -339,8 +339,10 @@ namespace ggui // ----------------- ImGui::title_with_seperator("BSP Only"); - + ImGui::DvarBool_External("Load and draw BSP entities", "bsp_load_entities"); + ImGui::Checkbox("Export reflections as tga files", &dvars::r_reflectionprobe_export_tga->current.enabled); TT(dvars::r_reflectionprobe_export_tga->description); + ImGui::Checkbox("Automatically compile reflections when building bsp", &dvars::bsp_gen_reflections_on_compile->current.enabled); TT(dvars::bsp_gen_reflections_on_compile->description); SPACING(0.0f, 4.0f); @@ -418,7 +420,10 @@ namespace ggui SPACING(0.0f, 4.0f); - ImGui::DragInt("Int 01", &dev_num_01, 0.1f); + ImGui::DragInt("Integer01", &dev_num_01, 0.1f); + ImGui::DragInt("Integer02", &dev_num_02, 0.1f); + ImGui::Checkbox("Bool01", &dev_bool_01); + ImGui::DragFloat3("Vec4 01", dev_vec_01, 25.0f); ImGui::ColorEdit4("Color 01", dev_color_01, ImGuiColorEditFlags_Float | ImGuiColorEditFlags_HDR); }); diff --git a/src/ggui/preferences.hpp b/src/ggui/preferences.hpp index 5332a94..94252a3 100644 --- a/src/ggui/preferences.hpp +++ b/src/ggui/preferences.hpp @@ -18,7 +18,9 @@ namespace ggui unsigned int m_child_count; public: + bool dev_bool_01; int dev_num_01; + int dev_num_02; float dev_vec_01[4]; float dev_color_01[4]; @@ -30,7 +32,9 @@ namespace ggui m_child_current = 0; m_child_count = 0; - dev_num_01 = 0; + dev_bool_01 = false; + dev_num_01 = 256; + dev_num_02 = 1; utils::vector::set_vec4(dev_vec_01, 1.0f); utils::vector::set_vec4(dev_color_01, 1.0f); } diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 95be18d..5d597e0 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -275,4 +275,27 @@ namespace utils { return ltrim(rtrim(s)); } + + // https://gist.github.com/maluoi/ade07688e741ab188841223b8ffeed22 (MIT!) + void tga_write(const char* filename, uint32_t width, uint32_t height, uint8_t* dataBGRA, uint8_t dataChannels, uint8_t fileChannels) + { + FILE* fp = NULL; + // MSVC prefers fopen_s, but it's not portable + //fp = fopen(filename, "wb"); + fopen_s(&fp, filename, "wb"); + if (fp == NULL) return; + + // You can find details about TGA headers here: http://www.paulbourke.net/dataformats/tga/ + uint8_t header[18] = { 0,0,2,0,0,0,0,0,0,0,0,0, (uint8_t)(width % 256), (uint8_t)(width / 256), (uint8_t)(height % 256), (uint8_t)(height / 256), (uint8_t)(fileChannels * 8), 0x20 }; + fwrite(&header, 18, 1, fp); + + for (uint32_t i = 0; i < width * height; i++) + { + for (uint32_t b = 0; b < fileChannels; b++) + { + fputc(dataBGRA[(i * dataChannels) + (b % dataChannels)], fp); + } + } + fclose(fp); + } } diff --git a/src/utils/utils.hpp b/src/utils/utils.hpp index 2499d31..a37ab4a 100644 --- a/src/utils/utils.hpp +++ b/src/utils/utils.hpp @@ -95,4 +95,6 @@ namespace utils std::string <rim(std::string &s); std::string &rtrim(std::string &s); std::string &trim(std::string &s); + + void tga_write(const char* filename, uint32_t width, uint32_t height, uint8_t* dataBGRA, uint8_t dataChannels = 4, uint8_t fileChannels = 3); }