From a3efba825f678d0784ff48f87a2d0985ec074366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Con=C3=A1l?= Date: Fri, 16 Feb 2024 15:39:57 -0500 Subject: [PATCH] Fixed loading --- CMakeLists.txt | 2 +- Renderer/Renderer.cpp | 12 +++---- Renderer/Renderer.hpp | 2 ++ Renderer/ShaderBuffer.hpp | 2 ++ Scene/BSP.cpp | 24 +++++++------- Scene/BSP.hpp | 39 +++++++++++----------- UI/UI.cpp | 14 +++++--- UI/UI.hpp | 4 +++ assets/shaders/ray.frag | 63 ++++++++++++++++++++++-------------- assets/shaders/ray.frag.spv | Bin 6060 -> 8072 bytes 10 files changed, 97 insertions(+), 65 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f572d7..45b6a13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) if(WIN32) set(CMAKE_CXX_FLAGS "-D_DEBUG") else() -set(CMAKE_CXX_FLAGS "-D_DEBUG -Wall") +set(CMAKE_CXX_FLAGS "-D_DEBUG -Wall -D_GLIBCXX_DEBUG -fsanitize=address") endif() project(Pleascach) diff --git a/Renderer/Renderer.cpp b/Renderer/Renderer.cpp index 82cebff..bf8363c 100644 --- a/Renderer/Renderer.cpp +++ b/Renderer/Renderer.cpp @@ -191,7 +191,11 @@ Renderer::Renderer(Window& win) : win(win) { command_buffer = std::make_unique(dev, queue_family); uniform_buffer = std::make_unique(phys_dev, dev); - shader_buffer = std::make_unique(phys_dev, dev); + + /* load map */ + bsp = std::make_unique("assets/maps/git.bsp"); + + shader_buffer = std::make_unique(phys_dev, dev, bsp->planes.size()); textures = createResources({ "assets/textures/oil.jpg", @@ -220,10 +224,6 @@ Renderer::Renderer(Window& win) : win(win) { { { -1.0,-1.0 } }, }); - /* load map */ - bsp = std::make_unique("assets/maps/git.bsp"); - - std::vector objects; objects.reserve(bsp->planes.size()); uint id = 0; for (const auto& plane : bsp->planes) { @@ -358,7 +358,7 @@ void Renderer::draw() { command_buffer->command_buffer.setScissor(0, scissor); command_buffer->bind(pipeline->layout, pipeline->desc_set); command_buffer->bind(*vertex_buffer); - shader_buffer->objects[0].center.y += glm::sin(time)/10.0; + //shader_buffer->objects[0].center.y += glm::sin(time)/10.0; command_buffer->command_buffer.draw(6, 1, 0, 0); /* draw User Interface stuff */ diff --git a/Renderer/Renderer.hpp b/Renderer/Renderer.hpp index 60c39c5..e0aa57e 100644 --- a/Renderer/Renderer.hpp +++ b/Renderer/Renderer.hpp @@ -60,6 +60,8 @@ struct Renderer { uint32_t current_image_idx; uint64_t frame = 0; + std::vector objects; + std::unique_ptr ui; Camera cam{ .pos = glm::vec3(0.0, 0.0, -1.0), }; diff --git a/Renderer/ShaderBuffer.hpp b/Renderer/ShaderBuffer.hpp index 0f4e54e..17d2e7f 100644 --- a/Renderer/ShaderBuffer.hpp +++ b/Renderer/ShaderBuffer.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include diff --git a/Scene/BSP.cpp b/Scene/BSP.cpp index 66fd87c..0f00088 100644 --- a/Scene/BSP.cpp +++ b/Scene/BSP.cpp @@ -3,6 +3,8 @@ #include #include +#include + using namespace Q3BSP; static inline void copy_data(void* file_data, std::string& dst, Lump& lump) { @@ -12,9 +14,11 @@ static inline void copy_data(void* file_data, std::string& dst, Lump& lump) { template static inline void copy_data(void* file_data, std::vector& dst, Lump& lump) { + Log::debug("Size: %zu (%zu)\n", lump.len/sizeof(T), lump.len); dst.resize(lump.len / sizeof(T)); + puts("ALLOC'D"); //Log::debug("%p %p\n", dst.data(), (u8*)file_data + lump.offset); - std::memcpy(dst.data(), (u8*)file_data + (size_t)lump.offset, lump.len); + std::memcpy(dst.data(), ((u8*)file_data) + lump.offset, lump.len); } BSP::BSP(const std::string& fname) : filename(fname) { @@ -28,11 +32,9 @@ BSP::BSP(const std::string& fname) : filename(fname) { Log::error("BSP file missing magic!\n"); } - size_t i = 0; - for (auto& lump : header->lumps) { - i++; - Log::debug("%i: Offset: %u | Length: %u\n", i, lump.offset, lump.len); - Log::debug("\tPointer: 0x%p\n", (u8*)file_data.data() + (size_t)lump.offset); + for (size_t i = 0; i < std::size(header->lumps); i++) { + Log::debug("%i: Offset: %u | Length: %u\n", i, header->lumps[i].offset, header->lumps[i].len); + Log::debug("\tPointer: 0x%p\n", (u8*)file_data.data() + (size_t)header->lumps[i].offset); } copy_data(file_data.data(), entities, header->entities); @@ -51,10 +53,10 @@ BSP::BSP(const std::string& fname) : filename(fname) { copy_data(file_data.data(), faces, header->faces); copy_data(file_data.data(), lightmaps, header->lightmaps); copy_data(file_data.data(), lightvols, header->lightvols); - - vis_info.sz_vectors = *reinterpret_cast(file_data.data() + header->vis_info.offset); - vis_info.vectors.resize(header->vis_info.len); - std::memcpy(vis_info.vectors.data(), file_data.data() + header->vis_info.offset + sizeof(u32), header->vis_info.len); - vis_info = *reinterpret_cast(file_data.data() + header->vis_info.offset); + vis_info.sz_vectors = reinterpret_cast(file_data.data() + header->vis_info.offset)[1]; + auto sz = header->vis_info.len; + Log::debug("Size: %u\n", sz); + vis_info.vectors.resize(sz); + std::memcpy(vis_info.vectors.data(), file_data.data() + header->vis_info.offset + 2*sizeof(u32), sz); } \ No newline at end of file diff --git a/Scene/BSP.hpp b/Scene/BSP.hpp index 7c54d9e..7595592 100644 --- a/Scene/BSP.hpp +++ b/Scene/BSP.hpp @@ -23,23 +23,25 @@ namespace Q3BSP { union { Lump lumps[17]; - Lump entities, - textures, - planes, - nodes, - leafs, - leaf_faces, - leaf_brushes, - models, - brushes, - brush_sides, - vertices, - mesh_vertices, - effects, - faces, - lightmaps, - lightvols, - vis_info; + struct { + Lump entities, + textures, + planes, + nodes, + leafs, + leaf_faces, + leaf_brushes, + models, + brushes, + brush_sides, + vertices, + mesh_vertices, + effects, + faces, + lightmaps, + lightvols, + vis_info; + }; }; }; @@ -108,7 +110,8 @@ namespace Q3BSP { }; struct Vertex { - glm::vec3 tex_coords; + glm::vec3 position; + glm::vec2 tex_coords; glm::vec2 lightmap_coords; glm::vec3 normal; glm::u8vec4 color; diff --git a/UI/UI.cpp b/UI/UI.cpp index 774e49f..17dd7d1 100644 --- a/UI/UI.cpp +++ b/UI/UI.cpp @@ -11,7 +11,7 @@ #include -UI::UI(Renderer* ren) : info { .flycam = ren->flycam, .time = ren->time, .rad = ren->rad, .cam = ren->cam }, dev(ren->dev) { +UI::UI(Renderer* ren) : info { .flycam = ren->flycam, .time = ren->time, .rad = ren->rad, .cam = ren->cam, .objects = ren->objects}, dev(ren->dev) { IMGUI_CHECKVERSION(); ImGui::CreateContext(); @@ -95,9 +95,15 @@ void UI::newFrame() { ImGui::SliderFloat("Theta", &info.cam.theta, 0.0, glm::pi()); ImGui::SliderFloat("Phi", &info.cam.phi, 0.0, glm::two_pi()); - ImGui::SliderFloat("X", &info.cam.pos.x, -10.0, 10.0); - ImGui::SliderFloat("Y", &info.cam.pos.y, -10.0, 10.0); - ImGui::SliderFloat("Z", &info.cam.pos.z, -10.0, 10.0); + ImGui::SliderFloat("X", &info.cam.pos.x, -1000.0, 1000.0); + ImGui::SliderFloat("Y", &info.cam.pos.y, -1000.0, 1000.0); + ImGui::SliderFloat("Z", &info.cam.pos.z, -1000.0, 1000.0); + + if(ImGui::CollapsingHeader("Objects")) { + for(const auto& obj : info.objects) { + ImGui::Text("(%f %f %f) + %f", obj.center.x, obj.center.y, obj.center.z, obj.dimensions.x); + } + } ImGui::End(); diff --git a/UI/UI.hpp b/UI/UI.hpp index f278bd2..a7188e7 100644 --- a/UI/UI.hpp +++ b/UI/UI.hpp @@ -5,6 +5,9 @@ #include +#include + +struct Object; struct Renderer; struct Camera; @@ -17,6 +20,7 @@ struct UI { float& rad; /* camera stuff */ Camera& cam; + const std::vector& objects; } info; vk::Device dev; diff --git a/assets/shaders/ray.frag b/assets/shaders/ray.frag index 55a16ad..6f2755c 100644 --- a/assets/shaders/ray.frag +++ b/assets/shaders/ray.frag @@ -41,8 +41,10 @@ layout (location = 0) in vec2 pos; layout (location = 0) out vec4 fragColor; /* joins two parts of a scene */ -float op_union(float v1, float v2) { - return min(v1, v2); +vec2 op_union(vec2 v1, float v2, float id) { + if(v1.x > v2) + return vec2(v2, id); + return v1; } /* subtracts sdf from scene */ @@ -51,7 +53,7 @@ float op_subtract(float v1, float v2) { } float sphere(vec3 p, vec3 c, float r) { - return length(p-c) - r; + return abs(length(p-c) - r); } float box(vec3 p, vec3 c, vec3 r) { @@ -61,7 +63,7 @@ float box(vec3 p, vec3 c, vec3 r) { } float plane(vec3 p, vec3 norm, float d) { - return dot(p, norm) + d; + return dot(p, normalize(norm)) + d; } float obj_to_sdf(vec3 p, uint n) { @@ -72,6 +74,9 @@ float obj_to_sdf(vec3 p, uint n) { case BOX: return box(p, objects[n].center.xyz, objects[n].dimensions.xyz); break; + case PLANE: + return plane(p, objects[n].center.xyz, objects[n].dimensions.x); + break; } } @@ -84,42 +89,48 @@ float terrain(vec3 p) { return box(p, vec3(0.0), vec3(100.0, 0.1, 100.0)) - abs(map(p.xz)); } -float sdf(vec3 pos) { - /*float d = 100000000.0; +/* */ +vec2 sdf(vec3 pos) { + vec2 d = vec2(100000000.0, -1.0); for(uint i = 0; i < n_objects; i++) { - d = op_union(d, obj_to_sdf(pos, i)); - }*/ + d = op_union(d, obj_to_sdf(pos, i), objects[n_objects].id); + } + /*float dsphere = sphere(pos, vec3(0.0, 3.0*sin(time)-10.0, 0.0), 1.0); float dbox = box(pos, vec3(0.0, -10.0, 0.0), vec3(10.0, 1.0, 10.0)); float dpellet = sphere(pos, vec3(0.0, 10.0*sin(time) - 10.0, 0.0), 0.1); float d = op_union(op_subtract(dbox, dsphere), dpellet);*/ + //d = op_union(d, plane(pos, normalize(vec3(1.0)), 50.0)); - return terrain(pos); + return d; } -float raycast(vec3 dir) { +vec2 raycast(vec3 dir) { float t = 0.0; for(int i = 0; i < MAX_STEPS; i++) { - float dt = sdf(cam_pos + dir * t); - if(dt < 0.0001*t) - return float(i)/MAX_STEPS; - else if(dt > 200.0) - return -1.0; - t += dt; + vec2 dt = sdf(cam_pos + dir * t); + if(dt.y == -1.0) + return dt; + if(dt.x < 0.0001*t) + return vec2(t, dt.y); +// return float(i)/MAX_STEPS; + else if(dt.x > 2000.0) + return vec2(dt.x, -1.0); + t += dt.x; } - return -1.0; + return vec2(0.0, -1.0); } vec3 norm(vec3 pos) { return normalize( vec3( - sdf(pos+eps.xyy), - sdf(pos+eps.yxy), - sdf(pos+eps.yyx) - ) - sdf(pos) + sdf(pos+eps.xyy).x, + sdf(pos+eps.yxy).x, + sdf(pos+eps.yyx).x + ) - sdf(pos).x ); } @@ -134,10 +145,12 @@ vec3 raygen() { void main() { vec3 dir = raygen(); - float d = raycast(dir); + vec2 d = raycast(dir); - if(d < 0.0) - fragColor = vec4(0.0); + vec3 p = d.x*dir + cam_pos; + + if(d.y != -1.0) + fragColor = vec4(d.y/n_objects); else - fragColor = vec4(abs(norm(d*dir+cam_pos)).r); + fragColor = vec4(0.0); } \ No newline at end of file diff --git a/assets/shaders/ray.frag.spv b/assets/shaders/ray.frag.spv index 1f7b5e9961a1908933a80cbd1b8f4aa8dd0e7123..27da9c72759743f36911fc73c6571389d8f2a308 100644 GIT binary patch literal 8072 zcmZQ(Qf6mhU}WHC;AMzpWB>y}1||j&lbeAJOuPF8`{)&yr0AKL8h{k=F))KDupB=F zI|B;?0~qHfX6At;lo=TqJQ*1n1Q?jX;_M6z49pD73``6R42Kv&YPlHPed68y{oQ?B z;m zfg!&jzBDg0KTo49&B!{<5QG?58JHQk85kJK3>m;|9uV6I!scaQV8~1XvDq2K7#J9e z3o=rRQZ>rbjIBX9&CnVo$IKwiz`#(z0AY(TFfb%T*`f>#3`JlzJA(`Z14B}N1za=8 z08p5K?3YB+FNLIE8cDw*NKH;+9-8%FH_L(K!1k~($TKi7iA%ds

$#)rZz}zelY_p0~>=r$c>4W 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 LptJ%K1Em!J%!TuM literal 6060 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