EBLAN_ENGINE/editor/src/editor_viewport.inl

194 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// The viewport displays frames produced by engine.exe itself, not a second raycaster.
std::string EditorApp::FindEnginePath() const {
char module[MAX_PATH]{}; GetModuleFileNameA(nullptr, module, MAX_PATH);
const auto directory = fs::path(module).parent_path();
std::error_code ec;
for (const auto& candidate : {directory / "engine.exe", directory.parent_path() / "engine.exe", fs::absolute("../engine.exe", ec)}) {
ec.clear();
if (fs::is_regular_file(candidate, ec)) return fs::absolute(candidate, ec).string();
}
return {};
}
void EditorApp::StopEnginePreview() {
if (previewProcess.hProcess) {
if (previewPacket) InterlockedExchange(&previewPacket->state, 3);
if (WaitForSingleObject(previewProcess.hProcess, 300) == WAIT_TIMEOUT) {
TerminateProcess(previewProcess.hProcess, 0);
WaitForSingleObject(previewProcess.hProcess, 1000);
}
CloseHandle(previewProcess.hProcess); CloseHandle(previewProcess.hThread);
previewProcess = {};
}
if (previewPacket) { UnmapViewOfFile(previewPacket); previewPacket = nullptr; }
if (previewMapping) { CloseHandle(previewMapping); previewMapping = nullptr; }
previewHasFrame = false;
}
bool EditorApp::EnsureEnginePreview() {
if (previewPacket) return true;
if (previewAttempted) return false;
previewAttempted = true;
auto fail = [&](const std::string& reason) {
previewError = reason; StopEnginePreview(); return false;
};
const auto engine = FindEnginePath();
if (engine.empty()) return fail("Не найден engine.exe. Соберите движок через build.bat.");
const std::string name = "Local\\EblanPreview-" + std::to_string(GetCurrentProcessId()) + "-" + std::to_string(GetTickCount64());
previewMapping = CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, sizeof(EnginePreviewPacket), name.c_str());
if (!previewMapping) return fail("Не удалось создать буфер игрового вида: " + std::to_string(GetLastError()));
previewPacket = static_cast<EnginePreviewPacket*>(MapViewOfFile(previewMapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(EnginePreviewPacket)));
if (!previewPacket) return fail("Не удалось открыть буфер игрового вида.");
// Initialize in place: no large packet on the thread's stack.
memset(previewPacket, 0, sizeof(*previewPacket));
previewPacket->magic = 0x56504245; previewPacket->version = 2; previewPacket->parentPid = GetCurrentProcessId();
std::string command = "\"" + engine + "\" \"--preview:" + name + "\"";
STARTUPINFOA si{}; si.cb = sizeof(si); si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE;
if (!CreateProcessA(engine.c_str(), command.data(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr,
fs::path(engine).parent_path().string().c_str(), &si, &previewProcess))
return fail("Не удалось запустить игровой вид: " + std::to_string(GetLastError()));
previewTexturesDirty = true; previewError.clear();
return true;
}
void EditorApp::UpdateEnginePreview() {
if (!EnsureEnginePreview()) return;
if (WaitForSingleObject(previewProcess.hProcess, 0) == WAIT_OBJECT_0) {
DWORD code = 0; GetExitCodeProcess(previewProcess.hProcess, &code);
previewError = "Игровой вид остановлен (код " + std::to_string(code) + "). Пересоберите engine.exe.";
StopEnginePreview(); return;
}
const LONG state = InterlockedCompareExchange(&previewPacket->state, 0, 0);
if (state == 1) {
if (GetTickCount64() - previewRequestTime > 5000) {
previewError = "Движок не ответил на запрос игрового вида."; StopEnginePreview();
}
return;
}
if (state == 2) {
previewPixels.assign(std::begin(previewPacket->pixels), std::end(previewPacket->pixels));
for (auto& pixel : previewPixels) pixel |= 0xFF000000; // DIB X -> opaque DX11 alpha
if (pd3dDevice) {
if (!previewTexture) {
D3D11_TEXTURE2D_DESC desc{};
desc.Width = 320; desc.Height = 240; desc.MipLevels = desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; desc.SampleDesc.Count = 1;
desc.Usage = D3D11_USAGE_DEFAULT; desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
if (FAILED(pd3dDevice->CreateTexture2D(&desc,nullptr,&previewTexture)) ||
FAILED(pd3dDevice->CreateShaderResourceView(previewTexture,nullptr,&previewSRV))) {
if (previewSRV) { previewSRV->Release(); previewSRV = nullptr; }
if (previewTexture) { previewTexture->Release(); previewTexture = nullptr; }
previewError = "Не удалось создать изображение игрового вида."; StopEnginePreview(); return;
}
}
ID3D11DeviceContext* context = nullptr; pd3dDevice->GetImmediateContext(&context);
context->UpdateSubresource(previewTexture,0,nullptr,previewPixels.data(),320*4,0);
context->Release();
}
previewHasFrame = true;
}
if (previewTexturesDirty) {
if (!ReadTextureSlots(GetTexturePackPath(), previewTextures)) {
previewError = "Повреждён пакет текстур. Исправьте его и нажмите «Повторить».";
StopEnginePreview(); return;
}
memcpy(previewPacket->textures,previewTextures.data(),previewTextures.size());
++previewPacket->textureRevision; previewTexturesDirty = false;
}
const bool scene = showCutsceneTimeline && !cutscene.events.empty() && previewSceneCamera;
previewPacket->x = scene ? cutscenePreview.x : previewSceneCamera ? playerStartX*64+32 : previewX;
previewPacket->y = scene ? cutscenePreview.y : previewSceneCamera ? playerStartY*64+32 : previewY;
previewPacket->angle = scene ? cutscenePreview.angle : previewSceneCamera ? playerStartAngle : previewAngle;
previewPacket->ceiling = ceilingColor; previewPacket->floor = floorColor;
previewPacket->spriteCount = static_cast<uint32_t>((std::min)(sprites.size(),size_t(32)));
memcpy(previewPacket->cells,map,sizeof(map));
if (!sprites.empty()) memcpy(previewPacket->sprites,sprites.data(),previewPacket->spriteCount*sizeof(SpriteEntry));
previewPacket->cinematic = scene && cutscenePreview.active;
previewPacket->fade = scene ? cutscenePreview.fade : 0;
previewPacket->lightingMode = static_cast<uint32_t>(lightingMode);
previewPacket->ambientLight = static_cast<uint32_t>(ambientLightLevel);
previewPacket->flashlight = isFlashlightOn ? 1 : 0;
previewPacket->lightCount = static_cast<uint32_t>((std::min)(lightSources.size(), size_t(MAX_EDITOR_LIGHTS)));
previewPacket->flashlightRadius=flashlightRadius;previewPacket->flashlightIntensity=flashlightIntensity;previewPacket->flashlightCone=flashlightCone;
if(!lightSources.empty()) memcpy(previewPacket->lights,lightSources.data(),previewPacket->lightCount*sizeof(EditorLightSource));
memcpy(previewPacket->bakedRGB,bakedLightRGB.data(),4096);
previewPacket->lightTick=static_cast<uint32_t>(GetTickCount64()*60/1000);
memset(previewPacket->subtitle,0,96);
if (scene && cutscenePreview.subtitle >= 0 && cutscenePreview.subtitle < static_cast<int>(cutscene.strings.size())) {
std::string encoded;
if (CutsceneEncode(cutscene.strings[cutscenePreview.subtitle],encoded)) memcpy(previewPacket->subtitle,encoded.data(),encoded.size());
}
previewRequestTime = GetTickCount64();
InterlockedExchange(&previewPacket->state,1);
}
static void ViewportPointSampler(const ImDrawList*, const ImDrawCmd* command) {
auto* sampler = static_cast<ID3D11SamplerState*>(command->UserCallbackData);
ID3D11Device* device = nullptr; sampler->GetDevice(&device);
ID3D11DeviceContext* context = nullptr; device->GetImmediateContext(&context);
context->PSSetSamplers(0,1,&sampler); context->Release(); device->Release();
}
void EditorApp::RenderSceneViewport() {
ImGui::TextUnformatted("ИГРОВОЙ ВИД / ASM");
if (ImGui::Button("К спавну")) {
previewSceneCamera = true; ScrubCutscene(0);
}
ImGui::SameLine();
if (ImGui::Checkbox("Камера сцены",&previewSceneCamera) && !previewSceneCamera) {
previewX = cutscenePreview.x; previewY = cutscenePreview.y; previewAngle = cutscenePreview.angle;
}
if (!pd3dDevice) { ImGui::TextUnformatted("Игровой вид доступен в окне редактора."); return; }
UpdateEnginePreview();
if (!previewError.empty()) {
ImGui::TextWrapped("%s",previewError.c_str());
if (ImGui::Button("Повторить")) { StopEnginePreview(); previewAttempted = false; previewError.clear(); }
}
if (!previewHasFrame || !previewSRV) { ImGui::TextUnformatted("Подготовка кадра движка…"); return; }
const auto available = ImGui::GetContentRegionAvail();
float width = (std::max)(1.0f,(std::min)(available.x,(available.y-48)*4/3));
ImVec2 size(width,width*3/4);
if (!previewSampler) {
D3D11_SAMPLER_DESC desc{}; desc.Filter=D3D11_FILTER_MIN_MAG_MIP_POINT;
desc.AddressU=desc.AddressV=desc.AddressW=D3D11_TEXTURE_ADDRESS_CLAMP; desc.MaxLOD=D3D11_FLOAT32_MAX;
pd3dDevice->CreateSamplerState(&desc,&previewSampler);
}
if (previewSampler) ImGui::GetWindowDrawList()->AddCallback(ViewportPointSampler,previewSampler);
ImGui::Image(reinterpret_cast<ImTextureID>(previewSRV),size);
if (previewSampler) ImGui::GetWindowDrawList()->AddCallback(ImDrawCallback_ResetRenderState,nullptr);
const bool hovered = ImGui::IsItemHovered();
if (hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) ImGui::SetWindowFocus();
// Free camera never writes to the player's spawn or to authored keyframes.
if (!cutscenePreviewPlaying && !ImGui::GetIO().WantTextInput &&
ImGui::IsWindowFocused() && !ImGui::IsAnyItemActive() && !ImGui::IsPopupOpen(nullptr,ImGuiPopupFlags_AnyPopupId)) {
const auto held = [](ImGuiKey key) { return ImGui::IsKeyDown(key) ? 1 : 0; };
const int forward = held(ImGuiKey_W)-held(ImGuiKey_S), strafe = held(ImGuiKey_D)-held(ImGuiKey_A);
const int turn = held(ImGuiKey_RightArrow)-held(ImGuiKey_LeftArrow);
const float dt = (std::min)(ImGui::GetIO().DeltaTime,0.1f);
if (forward || strafe || turn || (hovered && ImGui::IsMouseDragging(ImGuiMouseButton_Right))) {
if (previewSceneCamera) {
previewX = previewPacket ? previewPacket->x : playerStartX*64+32;
previewY = previewPacket ? previewPacket->y : playerStartY*64+32;
previewAngle = previewPacket ? previewPacket->angle : playerStartAngle;
previewSceneCamera = false;
}
previewTurn += turn*128*dt + (hovered && ImGui::IsMouseDown(ImGuiMouseButton_Right) ? ImGui::GetIO().MouseDelta.x*0.5 : 0);
int step=static_cast<int>(previewTurn); previewTurn-=step; previewAngle=(previewAngle+step)&511;
const double a = previewAngle*3.141592653589793/256;
const double speed = (ImGui::GetIO().KeyShift ? 360 : 180)*dt;
previewMoveX += (forward*std::cos(a)-strafe*std::sin(a))*speed;
previewMoveY += (forward*std::sin(a)+strafe*std::cos(a))*speed;
int dx=static_cast<int>(previewMoveX),dy=static_cast<int>(previewMoveY);
previewMoveX-=dx; previewMoveY-=dy;
auto free = [&](int x,int y) {
for (int oy : {-12,12}) for (int ox : {-12,12}) {
if(x+ox<0 || x+ox>=2048 || y+oy<0 || y+oy>=2048 || !IsWalkableCell(map[(y+oy)/64][(x+ox)/64])) return false;
}
return true;
};
if (free(previewX+dx,previewY)) previewX+=dx;
if (free(previewX,previewY+dy)) previewY+=dy;
}
}
ImGui::TextWrapped("Клик по виду: WASD, стрелки — осмотр. Shift — быстрее. Камера сцены — предпросмотр таймлайна.");
}