#include "Client.hpp" #include "render/index.hpp" #include "render/UI.hpp" #include "InputMap.hpp" #include "world/index.hpp" #include #include "../core/data/math.hpp" #include Client::Client(config::client::options& options): options(options) { } Client::~Client() { } void Client::run(server_handle* const localHandle) { if (!render::Load(window, options.preferVulkan, options.renderer)) return; window.setTargetFPS(options.window.targetFPS); InputMap inputs(window.getPtr()); Controllable player(window.getPtr(), inputs, options.control); Camera camera(&player, options.camera); auto pipeline = render::Renderer::Get(); pipeline->LightInvDir = glm::normalize(glm::vec3(-.5f, 2, -2)); render::Renderer::Get()->loadUI(window); auto world = world::client::Load(options.connection, localHandle, options.world); world->setContouring(contouring::load(options.contouring.idx, options.contouring.data)); state.contouring = world->getContouring(); //TODO: loop do { window.startFrame(); { // Update ZoneScopedN("Update"); static double lastTime = window.getTime(); const double partTime = window.getTime(); const float deltaTime = partTime - lastTime; inputs.toggle(state.capture_mouse, Input::Mouse); inputs.toggle(options.debugMenu.bar, Input::Debug); player.capture(state.capture_mouse, !render::UI::IsFocus(), deltaTime); if(player.velocity != glm::vec3(0) && ( !options.control.collide || !world->collide(player.position, player.velocity, options.voxel_density, options.voxel_density)) ) { player.position += player.velocity; const auto pos = player.position.as_voxel(options.voxel_density); if(pos != state.position.as_voxel(options.voxel_density)) { world->emit(world::action::Move(pos)); } state.position = player.position; } camera.update(); pipeline->lookFrom(camera); pipeline->LightInvDir = glm::vec3(glm::rotate(glm::mat4(1), deltaTime * .1f, glm::vec3(1, .5, .1)) * glm::vec4(pipeline->LightInvDir, 0)); { const auto ray_result = world->raycast(camera.getRay() * options.voxel_density); if(auto target = std::get_if(&ray_result)) { state.look_at = *target; } else { state.look_at = {}; } } if (state.capture_mouse) { if (state.look_at.has_value()) { ZoneScopedN("Edit"); const auto &tool = options.editor.tool; if (inputs.isPressing(Mouse::Left)) world->emit(world::action::FillCube( state.look_at.value().pos, world::Voxel(world::materials::AIR, tool.emptyAir * world::Voxel::DENSITY_MAX), tool.radius)); else if (inputs.isPressing(Mouse::Right)) world->emit(world::action::FillCube( state.look_at.value().pos, world::Voxel(tool.material, world::Voxel::DENSITY_MAX), tool.radius)); } if (inputs.isDown(Input::Throw)) { //FIXME: register entity type world->addEntity(entity_id(0), {state.position * options.voxel_density, glm::vec3(10, 0, 0)}); } } world->update(state.position.as_voxel(options.voxel_density), deltaTime); inputs.saveKeys(); lastTime = partTime; } { ZoneScopedN("UI"); const auto actions = render::UI::Get()->draw(options, state, reports); if (actions && render::UI::Actions::FPS) { window.setTargetFPS(options.window.targetFPS); } if (actions && render::UI::Actions::FullScreen) { window.setFullscreen(options.window.fullscreen); } if(actions && render::UI::Actions::ClearColor) { pipeline->setClearColor(options.renderer.clear_color); } if(actions && render::UI::Actions::RendererSharders) { pipeline->reloadShaders(options.renderer.voxel); } if(actions && render::UI::Actions::RendererTextures) { pipeline->reloadTextures(options.renderer.textures, options.renderer.mipMapLOD, options.renderer.anisotropy); } if(actions && render::UI::Actions::World) { //FIXME: server options world->setOptions(options.world); } if(actions && render::UI::Actions::Camera) { camera.setOptions(options.camera); } if(actions && render::UI::Actions::Control) { player.setOptions(options.control); } if(actions && render::UI::Actions::ChangeContouring) { state.contouring = NULL; world->setContouring(contouring::load(options.contouring.idx, options.contouring.data)); state.contouring = world->getContouring(); } } { // Rendering ZoneScopedN("Render"); pipeline->beginFrame(); reports.models_count = 0; reports.tris_count = 0; std::optional frustum; if(options.culling >= 0) { frustum = {camera.getFrustum()}; } const auto offset = state.position.raw_as_long(); { // Chunks const auto pass = pipeline->beginWorldPass(); const auto draw = [&](glm::mat4 model, buffer::Abstract *const buffer, const contouring::Abstract::area_info &area, const voxel_pos &pos) { pipeline->setCurvature(glm::vec4(pos, std::get<1>(area)), std::get<2>(area)); reports.models_count++; reports.tris_count += buffer->draw(pass(model)); }; if (options.culling > 0) { std::vector occlusion; const auto ratio = options.culling * 2; occlusion.reserve(glm::pow2(ratio * 2 - 1)); const auto [ch, cv] = player.getAngles(); const auto max_v = tan(options.camera.fov / 2.), max_h = Window::RATIO * max_v; for(int iv = -ratio + 1; iv < ratio; iv++) { const auto v = cv + max_v * iv / ratio; for(int ih = -ratio + 1; ih < ratio; ih++) { const auto h = ch + max_h * ih / ratio; occlusion.emplace_back(cos(v) * sin(h), sin(v), cos(v) * cos(h)); } } state.contouring->getModels(draw, player.position, options.camera.far, occlusion, offset, options.voxel_density); } else { state.contouring->getModels(draw, frustum, offset, options.voxel_density); } } { // Entities const auto pass = pipeline->beginEntityPass(); /*const auto draw = [&](const std::vector& models, buffer::Abstract *const buffer) { reports.models_count += models.size(); reports.tris_count += buffer->draw(pass(models)); };*/ //world->getEntitiesModels(draw, frustum, offset, options.voxel_density); } if(state.look_at.has_value()) { // Indicator const auto model = glm::scale(glm::translate(glm::scale(glm::mat4(1), 1.f / glm::vec3(options.voxel_density)), glm::vec3(state.look_at.value().pos.second + state.look_at.value().offset - offset * glm::llvec3(options.voxel_density)) - glm::vec3(.5 + options.editor.tool.radius)), glm::vec3(1 + options.editor.tool.radius * 2)); reports.models_count++; reports.tris_count += pipeline->drawIndicatorCube(model); } pipeline->endPass(); render::UI::Get()->render(); } { // Swap buffers ZoneScopedN("Swap"); pipeline->swapBuffer(window); inputs.poll(); FrameMarkNamed("Client"); } window.waitTargetFPS(); } while (!(inputs.isDown(Input::Quit) || window.shouldClose())); render::UI::Unload(); render::Renderer::Unload(); window.destroy(); contouring::save(options.contouring.idx, state.contouring, options.contouring.data); }