feat(editor): overhaul lighting UX with tool mode, live auto-bake, presets and radius gizmo
This commit is contained in:
parent
1efa1195cf
commit
432fccfd60
6 changed files with 427 additions and 114 deletions
Binary file not shown.
|
|
@ -1336,20 +1336,28 @@ void EditorApp::ApplyTool(int gx, int gy) {
|
|||
case TOOL_PLAYER_SPAWN:
|
||||
if (SetCell(gx, gy, CELL_EMPTY)) { playerStartX = gx; playerStartY = gy; }
|
||||
break;
|
||||
case TOOL_LIGHT_SPAWN:
|
||||
if (lightSources.size() < MAX_EDITOR_LIGHTS) {
|
||||
if (lightingMode == 0) lightingMode = 2;
|
||||
EditorLightSource nl;
|
||||
nl.x = gx * 64 + 32; nl.y = gy * 64 + 32;
|
||||
nl.radius = 240; nl.intensity = 220; nl.curIntensity = 220;
|
||||
nl.color = 0x00FFB040;
|
||||
nl.lightType = LIGHT_TYPE_POINT;
|
||||
nl.flags = LIGHT_FLAG_ACTIVE | LIGHT_FLAG_SHADOWS | LIGHT_FLAG_FLICKER;
|
||||
lightSources.push_back(nl);
|
||||
selectedLightIdx = static_cast<int>(lightSources.size()) - 1;
|
||||
selectedSpriteIdx = -1; selectedTransitionIdx = -1;
|
||||
if (autoBakeLighting) BakeLighting();
|
||||
statusMessage = "Источник света создан на клетке (" + std::to_string(gx) + ", " + std::to_string(gy) + ").";
|
||||
} else {
|
||||
statusMessage = "Лимит источников света карты: 13.";
|
||||
}
|
||||
break;
|
||||
case TOOL_SPRITE_SPAWN:
|
||||
if (pendingSpawnArchetype == 99) {
|
||||
if (lightSources.size() < MAX_EDITOR_LIGHTS) {
|
||||
if(lightingMode==0)lightingMode=2;
|
||||
EditorLightSource nl;
|
||||
nl.x = gx * 64 + 32; nl.y = gy * 64 + 32;
|
||||
lightSources.push_back(nl);
|
||||
selectedLightIdx = static_cast<int>(lightSources.size()) - 1;
|
||||
selectedSpriteIdx = -1; selectedTransitionIdx = -1;
|
||||
BakeLighting();
|
||||
statusMessage = "Источник света создан на карте.";
|
||||
} else {
|
||||
statusMessage = "Лимит источников света карты: 13.";
|
||||
}
|
||||
currentTool = TOOL_LIGHT_SPAWN;
|
||||
ApplyTool(gx, gy);
|
||||
break;
|
||||
}
|
||||
if (sprites.size() < MAX_LEVEL_SPRITES) {
|
||||
|
|
@ -1418,7 +1426,10 @@ void EditorApp::DuplicateSelectedEntity() {
|
|||
if(selectedLightIdx>=0 && selectedLightIdx<static_cast<int>(lightSources.size())) {
|
||||
if(lightSources.size()>=MAX_EDITOR_LIGHTS){statusMessage="Лимит: 13 источников карты.";return;}
|
||||
auto copy=lightSources[selectedLightIdx];copy.x=(std::min)(2047,copy.x+32);copy.y=(std::min)(2047,copy.y+32);
|
||||
PushUndo();lightSources.push_back(copy);selectedLightIdx=static_cast<int>(lightSources.size())-1;return;
|
||||
PushUndo();lightSources.push_back(copy);selectedLightIdx=static_cast<int>(lightSources.size())-1;
|
||||
if(autoBakeLighting) BakeLighting();
|
||||
statusMessage = "Источник света дублирован (Ctrl+D).";
|
||||
return;
|
||||
}
|
||||
if (selectedSpriteIdx >= 0 && selectedSpriteIdx < static_cast<int>(sprites.size())) {
|
||||
if (sprites.size() >= MAX_LEVEL_SPRITES) {
|
||||
|
|
@ -1437,7 +1448,11 @@ void EditorApp::DuplicateSelectedEntity() {
|
|||
|
||||
void EditorApp::DeleteSelectedEntity() {
|
||||
if(selectedLightIdx>=0 && selectedLightIdx<static_cast<int>(lightSources.size())) {
|
||||
PushUndo();lightSources.erase(lightSources.begin()+selectedLightIdx);selectedLightIdx=draggingLightIdx=-1;return;
|
||||
PushUndo();lightSources.erase(lightSources.begin()+selectedLightIdx);
|
||||
selectedLightIdx=draggingLightIdx=draggingLightRadiusIdx=-1;
|
||||
if(autoBakeLighting) BakeLighting();
|
||||
statusMessage = "Источник света удален (Del).";
|
||||
return;
|
||||
}
|
||||
if (selectedSpriteIdx >= 0 && selectedSpriteIdx < static_cast<int>(sprites.size())) {
|
||||
PushUndo();
|
||||
|
|
@ -1517,6 +1532,7 @@ void EditorApp::HandleHotkeys() {
|
|||
if (ImGui::IsKeyPressed(ImGuiKey_F, false)) currentTool = TOOL_FILL;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_P, false)) currentTool = TOOL_PLAYER_SPAWN;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_S, false)) currentTool = TOOL_SPRITE_SPAWN;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_L, false)) currentTool = TOOL_LIGHT_SPAWN;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
|
@ -1724,6 +1740,7 @@ void EditorApp::RenderMenuBar() {
|
|||
if (ImGui::MenuItem("Заливка области", "F", currentTool == TOOL_FILL)) currentTool = TOOL_FILL;
|
||||
if (ImGui::MenuItem("Спавн игрока", "P", currentTool == TOOL_PLAYER_SPAWN)) currentTool = TOOL_PLAYER_SPAWN;
|
||||
if (ImGui::MenuItem("Добавить спрайт", "S", currentTool == TOOL_SPRITE_SPAWN)) currentTool = TOOL_SPRITE_SPAWN;
|
||||
if (ImGui::MenuItem("Источник света", "L", currentTool == TOOL_LIGHT_SPAWN)) currentTool = TOOL_LIGHT_SPAWN;
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
|
|
@ -1915,6 +1932,7 @@ void EditorApp::RenderToolbar() {
|
|||
ToolButton(" [F] Заливка ", TOOL_FILL);
|
||||
ToolButton(" [P] Игрок ", TOOL_PLAYER_SPAWN);
|
||||
ToolButton(" [S] Спрайт ", TOOL_SPRITE_SPAWN);
|
||||
ToolButton(" [L] Свет ", TOOL_LIGHT_SPAWN);
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
|
||||
|
|
@ -2108,6 +2126,21 @@ void EditorApp::RenderGridCanvas() {
|
|||
|
||||
// 2b. Draw Light Sources on Map Canvas
|
||||
int hoveredLight = -1;
|
||||
int hoveredRadiusHandle = -1;
|
||||
|
||||
// Check if radius handle of selected light is hovered
|
||||
if (selectedLightIdx >= 0 && selectedLightIdx < static_cast<int>(lightSources.size())) {
|
||||
float lx = lightSources[selectedLightIdx].x / 64.0f;
|
||||
float ly = lightSources[selectedLightIdx].y / 64.0f;
|
||||
ImVec2 lcPos = ImVec2(gridOrigin.x + lx * tileSize, gridOrigin.y + ly * tileSize);
|
||||
float radPix = (lightSources[selectedLightIdx].radius / 64.0f) * tileSize;
|
||||
ImVec2 hPos = ImVec2(lcPos.x + radPix, lcPos.y);
|
||||
float hdx = mousePos.x - hPos.x, hdy = mousePos.y - hPos.y;
|
||||
if (std::sqrt(hdx * hdx + hdy * hdy) < 9.0f) {
|
||||
hoveredRadiusHandle = selectedLightIdx;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < lightSources.size(); ++i) {
|
||||
float lx = lightSources[i].x / 64.0f;
|
||||
float ly = lightSources[i].y / 64.0f;
|
||||
|
|
@ -2116,26 +2149,43 @@ void EditorApp::RenderGridCanvas() {
|
|||
float dx = mousePos.x - lcPos.x;
|
||||
float dy = mousePos.y - lcPos.y;
|
||||
float r = tileSize * 0.45f;
|
||||
if (std::sqrt(dx * dx + dy * dy) < r) {
|
||||
if (std::sqrt(dx * dx + dy * dy) < r && hoveredRadiusHandle < 0) {
|
||||
hoveredLight = static_cast<int>(i);
|
||||
}
|
||||
|
||||
uint32_t c = lightSources[i].color;
|
||||
uint8_t cr = (c >> 16) & 0xFF, cg = (c >> 8) & 0xFF, cb = c & 0xFF;
|
||||
|
||||
if (static_cast<int>(i) == selectedLightIdx || static_cast<int>(i) == hoveredLight) {
|
||||
float radPix = (lightSources[i].radius / 64.0f) * tileSize;
|
||||
drawList->AddCircle(lcPos, radPix, IM_COL32(255, 210, 40, 160), 48, 1.5f);
|
||||
drawList->AddCircleFilled(lcPos, radPix, IM_COL32(255, 210, 40, 18));
|
||||
drawList->AddCircle(lcPos, radPix, IM_COL32(cr, cg, cb, 180), 48, 1.8f);
|
||||
drawList->AddCircleFilled(lcPos, radPix, IM_COL32(cr, cg, cb, 22));
|
||||
|
||||
// Radius handle for selected light
|
||||
if (static_cast<int>(i) == selectedLightIdx) {
|
||||
ImVec2 hPos = ImVec2(lcPos.x + radPix, lcPos.y);
|
||||
bool hHover = (hoveredRadiusHandle == selectedLightIdx || draggingLightRadiusIdx == selectedLightIdx);
|
||||
drawList->AddCircleFilled(hPos, hHover ? 7.0f : 5.0f, IM_COL32(255, 255, 255, 255));
|
||||
drawList->AddCircle(hPos, hHover ? 7.0f : 5.0f, IM_COL32(cr, cg, cb, 255), 16, 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t c = lightSources[i].color;
|
||||
ImU32 lightCol = IM_COL32((c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF, 255);
|
||||
ImU32 haloCol = IM_COL32((c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF, 70);
|
||||
ImU32 lightCol = IM_COL32(cr, cg, cb, 255);
|
||||
ImU32 haloCol = IM_COL32(cr, cg, cb, 70);
|
||||
|
||||
drawList->AddCircleFilled(lcPos, tileSize * 0.38f, haloCol);
|
||||
drawList->AddCircleFilled(lcPos, tileSize * 0.22f, lightCol);
|
||||
drawList->AddCircleFilled(lcPos, tileSize * 0.08f, IM_COL32(255, 255, 255, 255));
|
||||
|
||||
// Light type badge: S = Static, F = Torch Flicker, P = Pulse, D = Dynamic
|
||||
char badge = (lightSources[i].lightType == LIGHT_TYPE_STATIC) ? 'S' :
|
||||
(lightSources[i].lightType == LIGHT_TYPE_PULSE) ? 'P' :
|
||||
(lightSources[i].flags & LIGHT_FLAG_FLICKER) ? 'F' : 'D';
|
||||
char badgeStr[2] = { badge, 0 };
|
||||
drawList->AddText(ImVec2(lcPos.x - 3.5f, lcPos.y - 6.5f), IM_COL32(0, 0, 0, 220), badgeStr);
|
||||
|
||||
if (static_cast<int>(i) == selectedLightIdx) {
|
||||
drawList->AddCircle(lcPos, tileSize * 0.42f, IM_COL32(255, 255, 0, 255), 24, 2.0f);
|
||||
drawList->AddCircle(lcPos, tileSize * 0.42f, IM_COL32(255, 230, 40, 255), 24, 2.5f);
|
||||
} else if (static_cast<int>(i) == hoveredLight) {
|
||||
drawList->AddCircle(lcPos, tileSize * 0.42f, IM_COL32(255, 255, 200, 200), 24, 1.5f);
|
||||
}
|
||||
|
|
@ -2188,18 +2238,24 @@ void EditorApp::RenderGridCanvas() {
|
|||
drawList->PopClipRect(); return;
|
||||
}
|
||||
if (placeCameraPoint) { drawList->PopClipRect(); return; }
|
||||
// Left Click: start action (drag light OR drag sprite OR paint)
|
||||
// Left Click: start action (drag radius OR drag light OR drag sprite OR paint)
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
if (hoveredLight >= 0) {
|
||||
if (hoveredRadiusHandle >= 0) {
|
||||
draggingLightRadiusIdx = hoveredRadiusHandle;
|
||||
PushUndo();
|
||||
} else if (hoveredLight >= 0) {
|
||||
draggingLightIdx = hoveredLight;
|
||||
selectedLightIdx = hoveredLight;
|
||||
selectedSpriteIdx = -1;
|
||||
selectedTransitionIdx = -1;
|
||||
PushUndo();
|
||||
} else if (currentTool == TOOL_LIGHT_SPAWN && inGrid) {
|
||||
PushUndo();
|
||||
ApplyTool(hoverGx, hoverGy);
|
||||
} else if (currentTool == TOOL_SPRITE_SPAWN && hoveredSprite >= 0) {
|
||||
// Start dragging existing sprite
|
||||
draggingSpriteIdx = hoveredSprite;
|
||||
selectedSpriteIdx = hoveredSprite; selectedLightIdx=-1;
|
||||
selectedSpriteIdx = hoveredSprite; selectedLightIdx = -1;
|
||||
selectedTransitionIdx = -1;
|
||||
PushUndo();
|
||||
} else if (inGrid) {
|
||||
|
|
@ -2207,7 +2263,7 @@ void EditorApp::RenderGridCanvas() {
|
|||
if (map[hoverGy][hoverGx] == CELL_PORTAL) {
|
||||
for (size_t i = 0; i < transitions.size(); ++i) {
|
||||
if (transitions[i].triggerCellX == hoverGx && transitions[i].triggerCellY == hoverGy) {
|
||||
selectedTransitionIdx = static_cast<int>(i); selectedLightIdx=-1;
|
||||
selectedTransitionIdx = static_cast<int>(i); selectedLightIdx = -1;
|
||||
selectedSpriteIdx = -1;
|
||||
break;
|
||||
}
|
||||
|
|
@ -2219,13 +2275,24 @@ void EditorApp::RenderGridCanvas() {
|
|||
}
|
||||
}
|
||||
|
||||
// Left Drag: continue painting OR move light OR move sprite
|
||||
// Left Drag: continue painting OR move light OR resize radius OR move sprite
|
||||
if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
|
||||
if (draggingLightIdx >= 0) {
|
||||
if (draggingLightRadiusIdx >= 0 && draggingLightRadiusIdx < static_cast<int>(lightSources.size())) {
|
||||
float lx = lightSources[draggingLightRadiusIdx].x / 64.0f;
|
||||
float ly = lightSources[draggingLightRadiusIdx].y / 64.0f;
|
||||
ImVec2 lcPos = ImVec2(gridOrigin.x + lx * tileSize, gridOrigin.y + ly * tileSize);
|
||||
float dx = mousePos.x - lcPos.x;
|
||||
float dy = mousePos.y - lcPos.y;
|
||||
float distPx = std::sqrt(dx * dx + dy * dy);
|
||||
int newRadius = std::clamp(static_cast<int>(distPx / tileSize * 64.0f), 32, 2048);
|
||||
lightSources[draggingLightRadiusIdx].radius = newRadius;
|
||||
if (autoBakeLighting && lightSources[draggingLightRadiusIdx].lightType == LIGHT_TYPE_STATIC) BakeLighting();
|
||||
} else if (draggingLightIdx >= 0 && draggingLightIdx < static_cast<int>(lightSources.size())) {
|
||||
float wx = (mousePos.x - gridOrigin.x) / tileSize;
|
||||
float wy = (mousePos.y - gridOrigin.y) / tileSize;
|
||||
lightSources[draggingLightIdx].x = std::clamp(static_cast<int32_t>(wx * 64.0f), 32, (MAP_GRID_W - 1) * 64);
|
||||
lightSources[draggingLightIdx].y = std::clamp(static_cast<int32_t>(wy * 64.0f), 32, (MAP_GRID_H - 1) * 64);
|
||||
if (autoBakeLighting && lightSources[draggingLightIdx].lightType == LIGHT_TYPE_STATIC) BakeLighting();
|
||||
} else if (draggingSpriteIdx >= 0) {
|
||||
float wx = (mousePos.x - gridOrigin.x) / tileSize;
|
||||
float wy = (mousePos.y - gridOrigin.y) / tileSize;
|
||||
|
|
@ -2238,12 +2305,16 @@ void EditorApp::RenderGridCanvas() {
|
|||
|
||||
// Left Release: end drag / stroke
|
||||
if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
|
||||
if (draggingLightIdx >= 0) {
|
||||
if (draggingLightIdx >= 0 || draggingLightRadiusIdx >= 0) {
|
||||
draggingLightIdx = -1;
|
||||
draggingLightRadiusIdx = -1;
|
||||
BakeLighting();
|
||||
}
|
||||
draggingSpriteIdx = -1;
|
||||
strokeSnapshotTaken = false;
|
||||
if (strokeSnapshotTaken) {
|
||||
strokeSnapshotTaken = false;
|
||||
if (autoBakeLighting) BakeLighting();
|
||||
}
|
||||
}
|
||||
|
||||
// Right-click erase follows the same portal lifecycle as every other tool.
|
||||
|
|
@ -2357,7 +2428,22 @@ void EditorApp::RenderTilePalette() {
|
|||
SpawnerBtn("+ Переключатель [E]", ENTITY_ARCHETYPE_SWITCH, ImVec4(0.70f, 0.40f, 0.10f, 1.0f));
|
||||
SpawnerBtn("+ Бочка (Взрывная)", ENTITY_ARCHETYPE_BARREL, ImVec4(0.70f, 0.25f, 0.10f, 1.0f));
|
||||
SpawnerBtn("+ Декорация (Проп)", ENTITY_ARCHETYPE_PROP, ImVec4(0.35f, 0.35f, 0.40f, 1.0f));
|
||||
SpawnerBtn("+ Источник света", 99, ImVec4(0.85f, 0.60f, 0.10f, 1.0f));
|
||||
|
||||
bool lightToolActive = (currentTool == TOOL_LIGHT_SPAWN);
|
||||
if (lightToolActive) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f);
|
||||
}
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.85f, 0.60f, 0.10f, 1.0f));
|
||||
if (ImGui::Button("+ Источник света [L]", ImVec2(-1, 24))) {
|
||||
currentTool = TOOL_LIGHT_SPAWN;
|
||||
statusMessage = "Режим размещения света: кликните на карте для создания источника.";
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
if (lightToolActive) {
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorApp::RenderHierarchy() {
|
||||
|
|
@ -2404,18 +2490,9 @@ void EditorApp::RenderHierarchy() {
|
|||
AddEntityItem("🕹 Рычаг / Переключатель", ENTITY_ARCHETYPE_SWITCH);
|
||||
AddEntityItem("💥 Взрывная бочка", ENTITY_ARCHETYPE_BARREL);
|
||||
AddEntityItem("🏺 Декорация / Проп", ENTITY_ARCHETYPE_PROP);
|
||||
if (ImGui::MenuItem("💡 Источник света")) {
|
||||
if (lightSources.size() < MAX_EDITOR_LIGHTS) {
|
||||
PushUndo();
|
||||
if(lightingMode==0)lightingMode=2;
|
||||
EditorLightSource nl;
|
||||
nl.x = (playerStartX + 1) * 64 + 32; nl.y = (playerStartY + 1) * 64 + 32;
|
||||
lightSources.push_back(nl);
|
||||
selectedLightIdx = static_cast<int>(lightSources.size()) - 1;
|
||||
selectedSpriteIdx = -1; selectedTransitionIdx = -1;
|
||||
BakeLighting();
|
||||
statusMessage = "Источник света создан на сцене.";
|
||||
}
|
||||
if (ImGui::MenuItem("💡 Источник света [L]")) {
|
||||
currentTool = TOOL_LIGHT_SPAWN;
|
||||
statusMessage = "Режим размещения света: кликните на карте для создания источника.";
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
|
@ -2533,13 +2610,51 @@ void EditorApp::RenderHierarchy() {
|
|||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
if(ImGui::TreeNodeEx("Источники света",ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
for(int i=0;i<static_cast<int>(lightSources.size());++i) {
|
||||
char label[100];snprintf(label,sizeof(label),"Свет #%d: %s##light_%d",i+1,
|
||||
lightSources[i].lightType==LIGHT_TYPE_STATIC?"постоянный":"динамический",i);
|
||||
if(ImGui::Selectable(label,selectedLightIdx==i)){selectedLightIdx=i;selectedSpriteIdx=selectedTransitionIdx=-1;}
|
||||
if(ImGui::TreeNodeEx("💡 Источники света", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
for(int i = 0; i < static_cast<int>(lightSources.size()); ++i) {
|
||||
const auto& ls = lightSources[i];
|
||||
const char* typeName = (ls.lightType == LIGHT_TYPE_STATIC) ? "Постоянный" :
|
||||
(ls.lightType == LIGHT_TYPE_PULSE) ? "Сирена" :
|
||||
(ls.flags & LIGHT_FLAG_FLICKER) ? "Факел" : "Точечный";
|
||||
char label[128];
|
||||
snprintf(label, sizeof(label), "Свет #%d: %s (%d, %d)##light_%d",
|
||||
i + 1, typeName, ls.x / 64, ls.y / 64, i);
|
||||
if (!MatchesSearch(label)) continue;
|
||||
|
||||
ImGui::PushID(i);
|
||||
uint32_t c = ls.color;
|
||||
ImVec4 dotColor = ImVec4(((c >> 16) & 255) / 255.f, ((c >> 8) & 255) / 255.f, (c & 255) / 255.f, 1.0f);
|
||||
ImGui::ColorButton("##dot", dotColor, ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoInputs, ImVec2(12, 12));
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Selectable(label, selectedLightIdx == i)) {
|
||||
selectedLightIdx = i;
|
||||
selectedSpriteIdx = selectedTransitionIdx = -1;
|
||||
}
|
||||
if (ImGui::BeginPopupContextItem()) {
|
||||
if (ImGui::MenuItem("🎯 Сфокусировать", "F")) {
|
||||
selectedLightIdx = i; selectedSpriteIdx = selectedTransitionIdx = -1;
|
||||
FocusSelectedEntity();
|
||||
}
|
||||
if (ImGui::MenuItem("📋 Дублировать", "Ctrl+D")) {
|
||||
selectedLightIdx = i; selectedSpriteIdx = selectedTransitionIdx = -1;
|
||||
DuplicateSelectedEntity();
|
||||
}
|
||||
if (ImGui::MenuItem("🧲 Выровнять по центру клетки")) {
|
||||
SnapLightToGrid(i);
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("🗑 Удалить источник", "Del")) {
|
||||
selectedLightIdx = i;
|
||||
DeleteSelectedEntity();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
if (ImGui::SmallButton("+ Добавить свет [L]")) {
|
||||
currentTool = TOOL_LIGHT_SPAWN;
|
||||
statusMessage = "Режим размещения света: кликните на карте для создания источника.";
|
||||
}
|
||||
if(ImGui::SmallButton("+ Добавить свет")) {pendingSpawnArchetype=99;currentTool=TOOL_SPRITE_SPAWN;statusMessage="Выберите место источника на карте.";}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
// 3. Sublevel Transitions Category
|
||||
|
|
@ -2586,9 +2701,10 @@ void EditorApp::RenderInspector() {
|
|||
ImGui::TextColored(ImVec4(0.2f, 0.8f, 1.0f, 1.0f), "ИНСПЕКТОР СВОЙСТВ (INSPECTOR)");
|
||||
ImGui::Separator();
|
||||
|
||||
if(selectedLightIdx>=0 && selectedLightIdx<static_cast<int>(lightSources.size())) {RenderLightingInspector();return;}
|
||||
RenderLightingSettings();
|
||||
ImGui::Separator();
|
||||
if (selectedLightIdx >= 0 && selectedLightIdx < static_cast<int>(lightSources.size())) {
|
||||
RenderLightingInspector();
|
||||
return;
|
||||
}
|
||||
|
||||
// Case 1: Sprite Entity Selected
|
||||
if (selectedSpriteIdx >= 0 && selectedSpriteIdx < static_cast<int>(sprites.size())) {
|
||||
|
|
@ -2870,6 +2986,10 @@ void EditorApp::RenderInspector() {
|
|||
if (ImGui::SmallButton("Хоррор")) { ceilingColor = 0x003B0710; floorColor = 0x001C1917; }
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("💡 Освещение карты (Lighting)", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
RenderLightingSettings();
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("⚙ Настройки проекта & Сборка", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
if (ImGui::Button("Настройки проекта (Жанр, Оружие)...", ImVec2(-1, 26))) {
|
||||
showProjectSettingsModal = true;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,18 @@ enum ToolMode {
|
|||
TOOL_ERASER,
|
||||
TOOL_FILL,
|
||||
TOOL_PLAYER_SPAWN,
|
||||
TOOL_SPRITE_SPAWN
|
||||
TOOL_SPRITE_SPAWN,
|
||||
TOOL_LIGHT_SPAWN
|
||||
};
|
||||
|
||||
enum LightPreset {
|
||||
LIGHT_PRESET_TORCH = 0,
|
||||
LIGHT_PRESET_LAMP,
|
||||
LIGHT_PRESET_ALARM,
|
||||
LIGHT_PRESET_CANDLE,
|
||||
LIGHT_PRESET_NEON,
|
||||
LIGHT_PRESET_TOXIC,
|
||||
LIGHT_PRESET_SPOTLIGHT
|
||||
};
|
||||
|
||||
// Full level snapshot for undo/redo (grid + spawn + palette + sprites + transitions)
|
||||
|
|
@ -126,6 +137,8 @@ public:
|
|||
int flashlightIntensity = 240;
|
||||
int flashlightCone = 45;
|
||||
bool showLightHeatmap = false;
|
||||
bool autoBakeLighting = true;
|
||||
int draggingLightRadiusIdx = -1;
|
||||
uint8_t bakedLightmap[MAP_TOTAL_CELLS] = { 0 };
|
||||
std::array<uint32_t,1024> bakedLightRGB{};
|
||||
uint64_t bakedLightingSignature = 0;
|
||||
|
|
@ -133,6 +146,8 @@ public:
|
|||
|
||||
void BakeLighting();
|
||||
void InitDefaultLights();
|
||||
void ApplyLightPreset(int lightIdx, int presetId);
|
||||
void SnapLightToGrid(int lightIdx);
|
||||
bool CastLightRay(int x1, int y1, int x2, int y2) const;
|
||||
bool LightingNeedsBake() const;
|
||||
LightingSettings GetLightingSettings() const;
|
||||
|
|
|
|||
|
|
@ -1,72 +1,250 @@
|
|||
void EditorApp::ApplyLightPreset(int lightIdx, int presetId) {
|
||||
if (lightIdx < 0 || lightIdx >= static_cast<int>(lightSources.size())) return;
|
||||
PushUndo();
|
||||
auto& l = lightSources[lightIdx];
|
||||
l.flags = LIGHT_FLAG_ACTIVE | LIGHT_FLAG_SHADOWS;
|
||||
switch (presetId) {
|
||||
case LIGHT_PRESET_TORCH: // 🕯 Факел
|
||||
l.color = 0x00FFB040;
|
||||
l.radius = 240;
|
||||
l.intensity = 220;
|
||||
l.curIntensity = 220;
|
||||
l.lightType = LIGHT_TYPE_POINT;
|
||||
l.flags |= LIGHT_FLAG_FLICKER;
|
||||
break;
|
||||
case LIGHT_PRESET_LAMP: // 💡 Лампа
|
||||
l.color = 0x00E0E8FF;
|
||||
l.radius = 280;
|
||||
l.intensity = 240;
|
||||
l.curIntensity = 240;
|
||||
l.lightType = LIGHT_TYPE_STATIC;
|
||||
break;
|
||||
case LIGHT_PRESET_ALARM: // 🚨 Тревожная сирена
|
||||
l.color = 0x00FF2020;
|
||||
l.radius = 300;
|
||||
l.intensity = 250;
|
||||
l.curIntensity = 250;
|
||||
l.lightType = LIGHT_TYPE_PULSE;
|
||||
break;
|
||||
case LIGHT_PRESET_CANDLE: // 🕯 Мягкая свеча
|
||||
l.color = 0x00FF9520;
|
||||
l.radius = 140;
|
||||
l.intensity = 180;
|
||||
l.curIntensity = 180;
|
||||
l.lightType = LIGHT_TYPE_POINT;
|
||||
l.flags |= LIGHT_FLAG_FLICKER;
|
||||
break;
|
||||
case LIGHT_PRESET_NEON: // 🔮 Киберпанк неон
|
||||
l.color = 0x0000E5FF;
|
||||
l.radius = 260;
|
||||
l.intensity = 230;
|
||||
l.curIntensity = 230;
|
||||
l.lightType = LIGHT_TYPE_POINT;
|
||||
break;
|
||||
case LIGHT_PRESET_TOXIC: // ☢ Токсик
|
||||
l.color = 0x0039FF14;
|
||||
l.radius = 250;
|
||||
l.intensity = 220;
|
||||
l.curIntensity = 220;
|
||||
l.lightType = LIGHT_TYPE_PULSE;
|
||||
break;
|
||||
case LIGHT_PRESET_SPOTLIGHT: // 🔦 Прожектор
|
||||
l.color = 0x00FFFFFF;
|
||||
l.radius = 380;
|
||||
l.intensity = 255;
|
||||
l.curIntensity = 255;
|
||||
l.lightType = LIGHT_TYPE_STATIC;
|
||||
break;
|
||||
}
|
||||
if (autoBakeLighting) BakeLighting();
|
||||
statusMessage = "Применен пресет света.";
|
||||
}
|
||||
|
||||
void EditorApp::SnapLightToGrid(int lightIdx) {
|
||||
if (lightIdx < 0 || lightIdx >= static_cast<int>(lightSources.size())) return;
|
||||
PushUndo();
|
||||
auto& l = lightSources[lightIdx];
|
||||
int cellX = std::clamp(l.x / 64, 0, MAP_GRID_W - 1);
|
||||
int cellY = std::clamp(l.y / 64, 0, MAP_GRID_H - 1);
|
||||
l.x = cellX * 64 + 32;
|
||||
l.y = cellY * 64 + 32;
|
||||
if (autoBakeLighting) BakeLighting();
|
||||
statusMessage = "Источник света выровнен по центру клетки (" + std::to_string(cellX) + ", " + std::to_string(cellY) + ").";
|
||||
}
|
||||
|
||||
void EditorApp::RenderLightingSettings() {
|
||||
if(!ImGui::CollapsingHeader("Освещение карты",ImGuiTreeNodeFlags_DefaultOpen)) return;
|
||||
auto edited=GetLightingSettings(); bool changed=false,activated=false;
|
||||
auto undoItem=[&](){activated|=ImGui::IsItemActivated();};
|
||||
const char* modes[]={"Без освещения","Только запечённый","Запечённый + динамический"};
|
||||
int mode=edited.mode;
|
||||
changed|=ImGui::Combo("Режим света",&mode,modes,3);undoItem();edited.mode=mode;
|
||||
int ambient=edited.ambient;
|
||||
changed|=ImGui::SliderInt("Фоновый свет",&ambient,0,255);undoItem();edited.ambient=ambient;
|
||||
bool flash=edited.flashlight!=0;
|
||||
changed|=ImGui::Checkbox("Фонарик игрока",&flash);undoItem();edited.flashlight=flash;
|
||||
if(flash) {
|
||||
int radius=edited.flashlightRadius,intensity=edited.flashlightIntensity,cone=edited.flashlightCone;
|
||||
changed|=ImGui::SliderInt("Дальность фонаря",&radius,32,2048);undoItem();
|
||||
changed|=ImGui::SliderInt("Яркость фонаря",&intensity,0,255);undoItem();
|
||||
changed|=ImGui::SliderInt("Полуугол фонаря",&cone,1,96);undoItem();
|
||||
edited.flashlightRadius=radius;edited.flashlightIntensity=intensity;edited.flashlightCone=cone;
|
||||
auto edited = GetLightingSettings();
|
||||
bool changed = false, activated = false;
|
||||
auto undoItem = [&]() { activated |= ImGui::IsItemActivated(); };
|
||||
|
||||
const char* modes[] = { "Без освещения (Fullbright)", "Только запечённый свет (Baked)", "Запечённый + Динамический (Full Physics)" };
|
||||
int mode = edited.mode;
|
||||
changed |= ImGui::Combo("Режим света", &mode, modes, 3); undoItem(); edited.mode = mode;
|
||||
|
||||
int ambient = edited.ambient;
|
||||
changed |= ImGui::SliderInt("Фоновый свет (Ambient)", &ambient, 0, 255); undoItem(); edited.ambient = ambient;
|
||||
|
||||
bool flash = (edited.flashlight != 0);
|
||||
changed |= ImGui::Checkbox("Фонарик игрока [F]", &flash); undoItem(); edited.flashlight = flash;
|
||||
if (flash) {
|
||||
int radius = edited.flashlightRadius, intensity = edited.flashlightIntensity, cone = edited.flashlightCone;
|
||||
changed |= ImGui::SliderInt("Дальность фонаря", &radius, 32, 2048); undoItem();
|
||||
changed |= ImGui::SliderInt("Яркость фонаря", &intensity, 0, 255); undoItem();
|
||||
changed |= ImGui::SliderInt("Полуугол луча", &cone, 1, 96); undoItem();
|
||||
edited.flashlightRadius = radius; edited.flashlightIntensity = intensity; edited.flashlightCone = cone;
|
||||
}
|
||||
if(activated)PushUndo();
|
||||
if(changed) {
|
||||
lightingMode=edited.mode;ambientLightLevel=edited.ambient;isFlashlightOn=edited.flashlight!=0;
|
||||
flashlightRadius=edited.flashlightRadius;flashlightIntensity=edited.flashlightIntensity;flashlightCone=edited.flashlightCone;
|
||||
|
||||
if (activated) PushUndo();
|
||||
if (changed) {
|
||||
lightingMode = edited.mode;
|
||||
ambientLightLevel = edited.ambient;
|
||||
isFlashlightOn = (edited.flashlight != 0);
|
||||
flashlightRadius = edited.flashlightRadius;
|
||||
flashlightIntensity = edited.flashlightIntensity;
|
||||
flashlightCone = edited.flashlightCone;
|
||||
if (autoBakeLighting) BakeLighting();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Checkbox("Авто-запекание (Live Realtime Update)", &autoBakeLighting);
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Мгновенно обновляет карту теней и 3D вид при любых изменениях источников и стен.");
|
||||
|
||||
ImGui::Checkbox("Карта запечённого света (Хитмап на 2D сетке)", &showLightHeatmap);
|
||||
|
||||
if (ImGui::Button("💡 Запечь постоянный свет сейчас", ImVec2(-1, 26))) {
|
||||
PushUndo();
|
||||
BakeLighting();
|
||||
}
|
||||
|
||||
if (LightingNeedsBake()) {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.45f, 0.2f, 1.0f), "⚠ Геометрия или источники изменены: требуется запекание");
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(0.2f, 1.0f, 0.4f, 1.0f), "✔ Постоянный свет запечён (1024 ячейки)");
|
||||
}
|
||||
if(ImGui::Button("Запечь постоянный свет",ImVec2(-1,0))) {PushUndo();BakeLighting();}
|
||||
if(LightingNeedsBake()) ImGui::TextWrapped("Геометрия или постоянный свет изменены: нужно запечь. При сохранении и F5 запекание выполняется автоматически.");
|
||||
else ImGui::TextDisabled("Постоянный свет запечён");
|
||||
ImGui::Checkbox("Карта запечённого света",&showLightHeatmap);
|
||||
ImGui::TextWrapped("Постоянные источники входят в запекание. Точечные, мерцающие и пульсирующие работают в динамическом режиме.");
|
||||
}
|
||||
|
||||
void EditorApp::RenderLightingInspector() {
|
||||
auto& light=lightSources[selectedLightIdx];
|
||||
ImGui::Text("Источник света #%d / %d",selectedLightIdx+1,MAX_EDITOR_LIGHTS);
|
||||
if(ImGui::Button("Удалить источник")) {
|
||||
PushUndo();lightSources.erase(lightSources.begin()+selectedLightIdx);selectedLightIdx=-1;return;
|
||||
}
|
||||
if (selectedLightIdx < 0 || selectedLightIdx >= static_cast<int>(lightSources.size())) return;
|
||||
auto& light = lightSources[selectedLightIdx];
|
||||
|
||||
const char* typeDesc = (light.lightType == LIGHT_TYPE_STATIC) ? "Постоянный (Запекаемый)" :
|
||||
(light.lightType == LIGHT_TYPE_PULSE) ? "Пульсирующий (Сирена)" :
|
||||
(light.flags & LIGHT_FLAG_FLICKER) ? "Мерцающий факел" : "Точечный (Динамический)";
|
||||
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.85f, 0.25f, 1.0f), "💡 Источник света #%d из %d: %s",
|
||||
selectedLightIdx + 1, static_cast<int>(lightSources.size()), typeDesc);
|
||||
ImGui::Separator();
|
||||
|
||||
// Quick Action Bar (Unity / Unreal style)
|
||||
if (ImGui::SmallButton("🎯 Фокус (F)")) { FocusSelectedEntity(); }
|
||||
ImGui::SameLine();
|
||||
if(ImGui::Button("Копировать")) {
|
||||
if(lightSources.size()<MAX_EDITOR_LIGHTS) {
|
||||
auto copy=light;copy.x=(std::min)(2047,copy.x+32);copy.y=(std::min)(2047,copy.y+32);
|
||||
PushUndo();lightSources.push_back(copy);selectedLightIdx=static_cast<int>(lightSources.size())-1;
|
||||
} else statusMessage="Лимит: 13 источников карты; фонарик и вспышки имеют отдельные места.";
|
||||
return;
|
||||
if (ImGui::SmallButton("📋 Дублировать (Ctrl+D)")) { DuplicateSelectedEntity(); }
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("🧲 К сетке (Snap)")) { SnapLightToGrid(selectedLightIdx); }
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("🗑 Удалить (Del)")) { DeleteSelectedEntity(); return; }
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::TextColored(ImVec4(0.4f, 0.9f, 1.0f, 1.0f), "Быстрые пресеты:");
|
||||
|
||||
// Preset Buttons
|
||||
if (ImGui::SmallButton("🕯 Факел")) ApplyLightPreset(selectedLightIdx, LIGHT_PRESET_TORCH);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("💡 Лампа")) ApplyLightPreset(selectedLightIdx, LIGHT_PRESET_LAMP);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("🚨 Сирена")) ApplyLightPreset(selectedLightIdx, LIGHT_PRESET_ALARM);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("🕯 Свеча")) ApplyLightPreset(selectedLightIdx, LIGHT_PRESET_CANDLE);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("🔮 Неон")) ApplyLightPreset(selectedLightIdx, LIGHT_PRESET_NEON);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("🔦 Прожектор")) ApplyLightPreset(selectedLightIdx, LIGHT_PRESET_SPOTLIGHT);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
EditorLightSource edited = light;
|
||||
bool changed = false, activated = false;
|
||||
auto undoItem = [&]() { activated |= ImGui::IsItemActivated(); };
|
||||
|
||||
// Toggle Active
|
||||
bool active = (edited.flags & LIGHT_FLAG_ACTIVE) != 0;
|
||||
changed |= ImGui::Checkbox("Включён на карте", &active); undoItem();
|
||||
edited.flags = (edited.flags & ~LIGHT_FLAG_ACTIVE) | (active ? LIGHT_FLAG_ACTIVE : 0);
|
||||
|
||||
// Coordinates with Cell info
|
||||
int curCellX = edited.x / 64;
|
||||
int curCellY = edited.y / 64;
|
||||
ImGui::Text("Клетка: [%d, %d] | Мировые координаты: (%d, %d)", curCellX, curCellY, edited.x, edited.y);
|
||||
float xCells = edited.x / 64.0f;
|
||||
float yCells = edited.y / 64.0f;
|
||||
if (ImGui::SliderFloat("Позиция X (клетки)", &xCells, 0.5f, 31.5f, "%.2f")) {
|
||||
edited.x = std::clamp(static_cast<int>(xCells * 64.0f), 32, (MAP_GRID_W - 1) * 64);
|
||||
changed = true;
|
||||
} undoItem();
|
||||
if (ImGui::SliderFloat("Позиция Y (клетки)", &yCells, 0.5f, 31.5f, "%.2f")) {
|
||||
edited.y = std::clamp(static_cast<int>(yCells * 64.0f), 32, (MAP_GRID_H - 1) * 64);
|
||||
changed = true;
|
||||
} undoItem();
|
||||
|
||||
// Radius & Intensity
|
||||
changed |= ImGui::SliderInt("Радиус (px)", &edited.radius, 32, 2048, "%d px"); undoItem();
|
||||
changed |= ImGui::SliderInt("Яркость", &edited.intensity, 0, 255, "%d / 255"); undoItem();
|
||||
edited.curIntensity = edited.intensity;
|
||||
|
||||
// Color Picker & Quick Swatches
|
||||
float color[] = { ((edited.color >> 16) & 255) / 255.f, ((edited.color >> 8) & 255) / 255.f, (edited.color & 255) / 255.f };
|
||||
if (ImGui::ColorEdit3("Цвет света", color)) {
|
||||
edited.color = (uint32_t(color[0] * 255) << 16) | (uint32_t(color[1] * 255) << 8) | uint32_t(color[2] * 255);
|
||||
changed = true;
|
||||
} undoItem();
|
||||
|
||||
// Quick Color Swatches
|
||||
ImGui::TextDisabled("Оттенки:");
|
||||
ImGui::SameLine();
|
||||
auto ColorSwatch = [&](const char* label, uint32_t hexColor) {
|
||||
float c[3] = { ((hexColor >> 16) & 255) / 255.f, ((hexColor >> 8) & 255) / 255.f, (hexColor & 255) / 255.f };
|
||||
if (ImGui::ColorButton(label, ImVec4(c[0], c[1], c[2], 1.0f), 0, ImVec2(18, 18))) {
|
||||
edited.color = hexColor;
|
||||
changed = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
};
|
||||
ColorSwatch("##warm_fire", 0x00FFB040);
|
||||
ColorSwatch("##cool_white", 0x00E0E8FF);
|
||||
ColorSwatch("##neon_cyan", 0x0000E5FF);
|
||||
ColorSwatch("##ruby_red", 0x00FF2020);
|
||||
ColorSwatch("##toxic_green", 0x0039FF14);
|
||||
ColorSwatch("##pure_white", 0x00FFFFFF);
|
||||
ImGui::NewLine();
|
||||
|
||||
// Light Type
|
||||
const char* types[] = { "Постоянный (запекание в lightmap)", "Точечный (динамический реалтайм)", "Пульсирующий (динамическая сирена)" };
|
||||
int type = (edited.lightType == LIGHT_TYPE_STATIC) ? 0 : (edited.lightType == LIGHT_TYPE_PULSE) ? 2 : 1;
|
||||
if (ImGui::Combo("Тип источника", &type, types, 3)) {
|
||||
edited.lightType = (type == 0) ? LIGHT_TYPE_STATIC : (type == 1) ? LIGHT_TYPE_POINT : LIGHT_TYPE_PULSE;
|
||||
changed = true;
|
||||
} undoItem();
|
||||
|
||||
// Behavior flags
|
||||
bool shadows = (edited.flags & LIGHT_FLAG_SHADOWS) != 0;
|
||||
changed |= ImGui::Checkbox("Тени от стен и дверей", &shadows); undoItem();
|
||||
edited.flags = (edited.flags & ~LIGHT_FLAG_SHADOWS) | (shadows ? LIGHT_FLAG_SHADOWS : 0);
|
||||
|
||||
if (edited.lightType != LIGHT_TYPE_STATIC) {
|
||||
bool flicker = (edited.flags & LIGHT_FLAG_FLICKER) != 0;
|
||||
changed |= ImGui::Checkbox("Мерцание огня / факела (Flicker)", &flicker); undoItem();
|
||||
edited.flags = (edited.flags & ~LIGHT_FLAG_FLICKER) | (flicker ? LIGHT_FLAG_FLICKER : 0);
|
||||
}
|
||||
EditorLightSource edited=light;bool changed=false,activated=false;
|
||||
auto undoItem=[&](){activated|=ImGui::IsItemActivated();};
|
||||
bool active=(edited.flags&LIGHT_FLAG_ACTIVE)!=0;
|
||||
changed|=ImGui::Checkbox("Включён",&active);undoItem();
|
||||
edited.flags=(edited.flags&~LIGHT_FLAG_ACTIVE)|(active?LIGHT_FLAG_ACTIVE:0);
|
||||
float x=edited.x/64.0f,y=edited.y/64.0f;
|
||||
if(ImGui::SliderFloat("Позиция X",&x,0,31.98f,"%.2f")){edited.x=static_cast<int>(x*64);changed=true;}undoItem();
|
||||
if(ImGui::SliderFloat("Позиция Y",&y,0,31.98f,"%.2f")){edited.y=static_cast<int>(y*64);changed=true;}undoItem();
|
||||
changed|=ImGui::SliderInt("Радиус",&edited.radius,32,2048);undoItem();
|
||||
changed|=ImGui::SliderInt("Интенсивность",&edited.intensity,0,255);undoItem();edited.curIntensity=edited.intensity;
|
||||
float color[]={((edited.color>>16)&255)/255.f,((edited.color>>8)&255)/255.f,(edited.color&255)/255.f};
|
||||
if(ImGui::ColorEdit3("Цвет света",color)) {
|
||||
edited.color=(uint32_t(color[0]*255)<<16)|(uint32_t(color[1]*255)<<8)|uint32_t(color[2]*255);changed=true;
|
||||
}undoItem();
|
||||
const char* types[]={"Постоянный (запекание)","Точечный (динамический)","Пульсирующий (динамический)"};
|
||||
int type=edited.lightType==LIGHT_TYPE_STATIC?0:edited.lightType==LIGHT_TYPE_PULSE?2:1;
|
||||
if(ImGui::Combo("Тип света",&type,types,3)){edited.lightType=type==0?LIGHT_TYPE_STATIC:type==1?LIGHT_TYPE_POINT:LIGHT_TYPE_PULSE;changed=true;}undoItem();
|
||||
bool shadows=(edited.flags&LIGHT_FLAG_SHADOWS)!=0,flicker=(edited.flags&LIGHT_FLAG_FLICKER)!=0;
|
||||
changed|=ImGui::Checkbox("Тени от стен и дверей",&shadows);undoItem();
|
||||
edited.flags=(edited.flags&~LIGHT_FLAG_SHADOWS)|(shadows?LIGHT_FLAG_SHADOWS:0);
|
||||
if(edited.lightType!=LIGHT_TYPE_STATIC) {
|
||||
changed|=ImGui::Checkbox("Мерцание факела",&flicker);undoItem();
|
||||
edited.flags=(edited.flags&~LIGHT_FLAG_FLICKER)|(flicker?LIGHT_FLAG_FLICKER:0);
|
||||
|
||||
if (activated) PushUndo();
|
||||
if (changed) {
|
||||
light = edited;
|
||||
if (autoBakeLighting) BakeLighting();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
if (ImGui::CollapsingHeader("Глобальное освещение карты")) {
|
||||
RenderLightingSettings();
|
||||
}
|
||||
if(activated)PushUndo();
|
||||
if(changed)light=edited;
|
||||
RenderLightingSettings();
|
||||
}
|
||||
|
|
|
|||
BIN
engine.exe
BIN
engine.exe
Binary file not shown.
BIN
main.obj
BIN
main.obj
Binary file not shown.
Loading…
Add table
Reference in a new issue