[nostalgia/core/studio/tilesheet] Add line drawing tool
All checks were successful
Build / build (push) Successful in 3m36s
All checks were successful
Build / build (push) Successful in 3m36s
This commit is contained in:
parent
b5954f15c5
commit
5351e9aa0a
@ -2,10 +2,64 @@
|
|||||||
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#include "drawcommand.hpp"
|
#include "drawcommand.hpp"
|
||||||
|
|
||||||
namespace nostalgia::gfx {
|
namespace nostalgia::gfx {
|
||||||
|
|
||||||
|
constexpr void iterateLine(ox::Point const &a, ox::Point const &b, auto const &f) noexcept {
|
||||||
|
auto const rise = b.y - a.y;
|
||||||
|
auto const run = b.x - a.x;
|
||||||
|
auto const slope = static_cast<double>(rise) / static_cast<double>(run);
|
||||||
|
auto y = static_cast<double>(a.y);
|
||||||
|
auto const w = abs(run);
|
||||||
|
if (rise > 0) {
|
||||||
|
if (run > 0) {
|
||||||
|
auto constexpr xmod = 1;
|
||||||
|
for (int32_t i = 0; i < w; ++i) {
|
||||||
|
auto const climb = static_cast<int32_t>(ceil(y + slope - y));
|
||||||
|
f(a.x + i * xmod, static_cast<int32_t>(y), climb * xmod);
|
||||||
|
y += slope * xmod;
|
||||||
|
}
|
||||||
|
} else if (run < 0) {
|
||||||
|
auto constexpr xmod = -1;
|
||||||
|
for (int32_t i = 0; i < w; ++i) {
|
||||||
|
auto const climb = static_cast<int32_t>(floor(y + slope - y));
|
||||||
|
f(a.x + i * xmod, static_cast<int32_t>(y), climb * xmod);
|
||||||
|
y += slope * xmod;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
f(a.x, a.y, rise);
|
||||||
|
}
|
||||||
|
} else if (rise < 0) {
|
||||||
|
if (run > 0) {
|
||||||
|
auto constexpr xmod = 1;
|
||||||
|
for (int32_t i = 0; i < w; ++i) {
|
||||||
|
auto const climb = static_cast<int32_t>(floor(y + slope - y));
|
||||||
|
f(a.x + i * xmod, static_cast<int32_t>(y), climb * xmod);
|
||||||
|
y += slope * xmod;
|
||||||
|
}
|
||||||
|
} else if (run < 0) {
|
||||||
|
auto constexpr xmod = -1;
|
||||||
|
for (int32_t i = 0; i < w; ++i) {
|
||||||
|
auto const climb = static_cast<int32_t>(ceil(y + slope - y));
|
||||||
|
f(a.x + i * xmod, static_cast<int32_t>(y), climb * xmod);
|
||||||
|
y += slope * xmod;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
f(a.x, a.y, rise - 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto const xmod = run > 0 ? 1 : -1;
|
||||||
|
for (int32_t i = 0; i < w; ++i) {
|
||||||
|
f(a.x + i * xmod, static_cast<int32_t>(y), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
DrawCommand::DrawCommand(
|
DrawCommand::DrawCommand(
|
||||||
TileSheet &img,
|
TileSheet &img,
|
||||||
TileSheet::SubSheetIdx subSheetIdx,
|
TileSheet::SubSheetIdx subSheetIdx,
|
||||||
@ -21,7 +75,7 @@ DrawCommand::DrawCommand(
|
|||||||
DrawCommand::DrawCommand(
|
DrawCommand::DrawCommand(
|
||||||
TileSheet &img,
|
TileSheet &img,
|
||||||
TileSheet::SubSheetIdx subSheetIdx,
|
TileSheet::SubSheetIdx subSheetIdx,
|
||||||
ox::Vector<std::size_t> const&idxList,
|
ox::SpanView<std::size_t> const&idxList,
|
||||||
int palIdx) noexcept:
|
int palIdx) noexcept:
|
||||||
m_img(img),
|
m_img(img),
|
||||||
m_subSheetIdx(std::move(subSheetIdx)),
|
m_subSheetIdx(std::move(subSheetIdx)),
|
||||||
@ -32,11 +86,12 @@ DrawCommand::DrawCommand(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DrawCommand::append(std::size_t idx) noexcept {
|
bool DrawCommand::append(std::size_t const idx) noexcept {
|
||||||
auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
|
auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
|
||||||
if (m_changes.back().value->idx != idx && getPixel(subsheet, idx) != m_palIdx) {
|
if (m_changes.back().value->idx != idx && getPixel(subsheet, idx) != m_palIdx) {
|
||||||
// duplicate entries are bad
|
// duplicate entries are bad
|
||||||
auto existing = ox::find_if(m_changes.cbegin(), m_changes.cend(), [idx](auto const&c) {
|
auto existing = find_if(
|
||||||
|
m_changes.cbegin(), m_changes.cend(), [idx](auto const&c) {
|
||||||
return c.idx == idx;
|
return c.idx == idx;
|
||||||
});
|
});
|
||||||
if (existing == m_changes.cend()) {
|
if (existing == m_changes.cend()) {
|
||||||
@ -48,7 +103,7 @@ bool DrawCommand::append(std::size_t idx) noexcept {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DrawCommand::append(ox::Vector<std::size_t> const&idxList) noexcept {
|
bool DrawCommand::append(ox::SpanView<std::size_t> const&idxList) noexcept {
|
||||||
auto out = false;
|
auto out = false;
|
||||||
for (auto idx : idxList) {
|
for (auto idx : idxList) {
|
||||||
out = append(idx) || out;
|
out = append(idx) || out;
|
||||||
@ -56,6 +111,25 @@ bool DrawCommand::append(ox::Vector<std::size_t> const&idxList) noexcept {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DrawCommand::lineUpdate(ox::Point a, ox::Point b) noexcept {
|
||||||
|
std::ignore = undo();
|
||||||
|
m_changes.clear();
|
||||||
|
auto &ss = getSubSheet(m_img, m_subSheetIdx);
|
||||||
|
if (a.x < b.x) { ++b.x; }
|
||||||
|
if (a.y < b.y) { ++b.y; }
|
||||||
|
if (a.x > b.x) { --b.x; }
|
||||||
|
iterateLine(a, b, [this, &ss](int32_t const x, int32_t const y, int32_t const h) {
|
||||||
|
int const mod = h < 0 ? -1 : 1;
|
||||||
|
for (int32_t i{}; i < abs(h); ++i) {
|
||||||
|
auto const idx = ptToIdx(x, y + i * mod, ss.columns * TileWidth);
|
||||||
|
if (idx < ss.pixels.size()) {
|
||||||
|
m_changes.emplace_back(static_cast<uint32_t>(idx), getPixel(ss, idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
std::ignore = redo();
|
||||||
|
}
|
||||||
|
|
||||||
ox::Error DrawCommand::redo() noexcept {
|
ox::Error DrawCommand::redo() noexcept {
|
||||||
auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
|
auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
|
||||||
for (auto const&c : m_changes) {
|
for (auto const&c : m_changes) {
|
||||||
|
@ -33,12 +33,14 @@ class DrawCommand: public TileSheetCommand {
|
|||||||
DrawCommand(
|
DrawCommand(
|
||||||
TileSheet &img,
|
TileSheet &img,
|
||||||
TileSheet::SubSheetIdx subSheetIdx,
|
TileSheet::SubSheetIdx subSheetIdx,
|
||||||
ox::Vector<std::size_t> const&idxList,
|
ox::SpanView<std::size_t> const&idxList,
|
||||||
int palIdx) noexcept;
|
int palIdx) noexcept;
|
||||||
|
|
||||||
bool append(std::size_t idx) noexcept;
|
bool append(std::size_t idx) noexcept;
|
||||||
|
|
||||||
bool append(ox::Vector<std::size_t> const&idxList) noexcept;
|
bool append(ox::SpanView<std::size_t> const&idxList) noexcept;
|
||||||
|
|
||||||
|
void lineUpdate(ox::Point a, ox::Point b) noexcept;
|
||||||
|
|
||||||
ox::Error redo() noexcept final;
|
ox::Error redo() noexcept final;
|
||||||
|
|
||||||
|
@ -152,6 +152,12 @@ void TileSheetEditorImGui::keyStateChanged(turbine::Key const key, bool const do
|
|||||||
setCutEnabled(false);
|
setCutEnabled(false);
|
||||||
setPasteEnabled(false);
|
setPasteEnabled(false);
|
||||||
m_model.clearSelection();
|
m_model.clearSelection();
|
||||||
|
} else if (key == turbine::Key::Alpha_E) {
|
||||||
|
m_tool = TileSheetTool::Line;
|
||||||
|
setCopyEnabled(false);
|
||||||
|
setCutEnabled(false);
|
||||||
|
setPasteEnabled(false);
|
||||||
|
m_model.clearSelection();
|
||||||
} else if (key == turbine::Key::Alpha_S) {
|
} else if (key == turbine::Key::Alpha_S) {
|
||||||
m_tool = TileSheetTool::Select;
|
m_tool = TileSheetTool::Select;
|
||||||
setCopyEnabled(true);
|
setCopyEnabled(true);
|
||||||
@ -214,7 +220,7 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept {
|
|||||||
ImGui::BeginChild("Controls", {s_palViewWidth - 8, paneSize.y}, true);
|
ImGui::BeginChild("Controls", {s_palViewWidth - 8, paneSize.y}, true);
|
||||||
{
|
{
|
||||||
auto const controlsSize = ImGui::GetContentRegionAvail();
|
auto const controlsSize = ImGui::GetContentRegionAvail();
|
||||||
ImGui::BeginChild("ToolBox", {168, 32}, true);
|
ImGui::BeginChild("ToolBox", {0, 32}, true);
|
||||||
{
|
{
|
||||||
auto const btnSz = ImVec2{45, 14};
|
auto const btnSz = ImVec2{45, 14};
|
||||||
if (ImGui::Selectable("Select", m_tool == TileSheetTool::Select, 0, btnSz)) {
|
if (ImGui::Selectable("Select", m_tool == TileSheetTool::Select, 0, btnSz)) {
|
||||||
@ -230,9 +236,16 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept {
|
|||||||
m_tool = TileSheetTool::Fill;
|
m_tool = TileSheetTool::Fill;
|
||||||
m_model.clearSelection();
|
m_model.clearSelection();
|
||||||
}
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Selectable("Line", m_tool == TileSheetTool::Line, 0, btnSz)) {
|
||||||
|
m_tool = TileSheetTool::Line;
|
||||||
|
m_model.clearSelection();
|
||||||
|
}
|
||||||
|
//ImGui::SameLine();
|
||||||
|
//size_t i{};
|
||||||
|
//ig::ComboBox("##Operations", ox::Array<ox::CStringView, 1>{"Operations"}, i);
|
||||||
}
|
}
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::BeginChild("OperationsBox", {0, 32}, true);
|
ImGui::BeginChild("OperationsBox", {0, 32}, true);
|
||||||
{
|
{
|
||||||
auto constexpr btnSz = ImVec2{55, 16};
|
auto constexpr btnSz = ImVec2{55, 16};
|
||||||
@ -448,6 +461,9 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
|
|||||||
case TileSheetTool::Fill:
|
case TileSheetTool::Fill:
|
||||||
m_view.clickFill(fbSize, clickPos(winPos, mousePos));
|
m_view.clickFill(fbSize, clickPos(winPos, mousePos));
|
||||||
break;
|
break;
|
||||||
|
case TileSheetTool::Line:
|
||||||
|
m_view.clickLine(fbSize, clickPos(winPos, mousePos));
|
||||||
|
break;
|
||||||
case TileSheetTool::Select:
|
case TileSheetTool::Select:
|
||||||
m_view.clickSelect(fbSize, clickPos(winPos, mousePos));
|
m_view.clickSelect(fbSize, clickPos(winPos, mousePos));
|
||||||
break;
|
break;
|
||||||
|
@ -141,10 +141,14 @@ size_t TileSheetEditorModel::palettePage() const noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TileSheetEditorModel::drawCommand(ox::Point const &pt, std::size_t const palIdx) noexcept {
|
void TileSheetEditorModel::drawCommand(ox::Point const &pt, std::size_t const palIdx) noexcept {
|
||||||
|
if (m_lastDrawUpdatePt == pt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
auto const &activeSubSheet = getSubSheet(m_img, m_activeSubsSheetIdx);
|
auto const &activeSubSheet = getSubSheet(m_img, m_activeSubsSheetIdx);
|
||||||
if (pt.x >= activeSubSheet.columns * TileWidth || pt.y >= activeSubSheet.rows * TileHeight) {
|
if (pt.x >= activeSubSheet.columns * TileWidth || pt.y >= activeSubSheet.rows * TileHeight) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
m_lastDrawUpdatePt = pt;
|
||||||
auto const idx = gfx::idx(activeSubSheet, pt);
|
auto const idx = gfx::idx(activeSubSheet, pt);
|
||||||
if (m_ongoingDrawCommand) {
|
if (m_ongoingDrawCommand) {
|
||||||
m_updated = m_updated || m_ongoingDrawCommand->append(idx);
|
m_updated = m_updated || m_ongoingDrawCommand->append(idx);
|
||||||
@ -154,6 +158,26 @@ void TileSheetEditorModel::drawCommand(ox::Point const&pt, std::size_t const pal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TileSheetEditorModel::drawLineCommand(ox::Point const &pt, std::size_t const palIdx) noexcept {
|
||||||
|
if (m_lastDrawUpdatePt == pt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto const &activeSubSheet = getSubSheet(m_img, m_activeSubsSheetIdx);
|
||||||
|
if (pt.x >= activeSubSheet.columns * TileWidth || pt.y >= activeSubSheet.rows * TileHeight) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_lastDrawUpdatePt = pt;
|
||||||
|
auto const idx = gfx::idx(activeSubSheet, pt);
|
||||||
|
if (m_ongoingDrawCommand) {
|
||||||
|
m_ongoingDrawCommand->lineUpdate(m_lineStartPt, pt);
|
||||||
|
m_updated = true;
|
||||||
|
} else if (getPixel(activeSubSheet, idx) != palIdx) {
|
||||||
|
std::ignore = pushCommand(ox::make<DrawCommand>(
|
||||||
|
m_img, m_activeSubsSheetIdx, idx, static_cast<int>(palIdx)));
|
||||||
|
m_lineStartPt = pt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TileSheetEditorModel::endDrawCommand() noexcept {
|
void TileSheetEditorModel::endDrawCommand() noexcept {
|
||||||
m_ongoingDrawCommand = nullptr;
|
m_ongoingDrawCommand = nullptr;
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,8 @@ class TileSheetEditorModel: public ox::SignalHandler {
|
|||||||
class DrawCommand *m_ongoingDrawCommand = nullptr;
|
class DrawCommand *m_ongoingDrawCommand = nullptr;
|
||||||
studio::SelectionTracker m_selTracker;
|
studio::SelectionTracker m_selTracker;
|
||||||
ox::Optional<studio::Selection> m_selection;
|
ox::Optional<studio::Selection> m_selection;
|
||||||
|
ox::Point m_lineStartPt;
|
||||||
|
ox::Point m_lastDrawUpdatePt;
|
||||||
bool m_updated = false;
|
bool m_updated = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -71,6 +73,8 @@ class TileSheetEditorModel: public ox::SignalHandler {
|
|||||||
|
|
||||||
void drawCommand(ox::Point const&pt, std::size_t palIdx) noexcept;
|
void drawCommand(ox::Point const&pt, std::size_t palIdx) noexcept;
|
||||||
|
|
||||||
|
void drawLineCommand(ox::Point const&pt, std::size_t palIdx) noexcept;
|
||||||
|
|
||||||
void endDrawCommand() noexcept;
|
void endDrawCommand() noexcept;
|
||||||
|
|
||||||
void addSubsheet(TileSheet::SubSheetIdx const&parentIdx) noexcept;
|
void addSubsheet(TileSheet::SubSheetIdx const&parentIdx) noexcept;
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
namespace nostalgia::gfx {
|
namespace nostalgia::gfx {
|
||||||
|
|
||||||
TileSheetEditorView::TileSheetEditorView(studio::StudioContext &sctx, ox::StringView path, studio::UndoStack &undoStack):
|
TileSheetEditorView::TileSheetEditorView(
|
||||||
|
studio::StudioContext &sctx, ox::StringViewCR path, studio::UndoStack &undoStack):
|
||||||
m_model(sctx, path, undoStack),
|
m_model(sctx, path, undoStack),
|
||||||
m_pixelsDrawer(m_model) {
|
m_pixelsDrawer(m_model) {
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
@ -76,6 +77,11 @@ void TileSheetEditorView::clickDraw(ox::Vec2 const&paneSize, ox::Vec2 const&clic
|
|||||||
m_model.drawCommand(pt, m_palIdx);
|
m_model.drawCommand(pt, m_palIdx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TileSheetEditorView::clickLine(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept {
|
||||||
|
auto const pt = clickPoint(paneSize, clickPos);
|
||||||
|
m_model.drawLineCommand(pt, m_palIdx);
|
||||||
|
}
|
||||||
|
|
||||||
void TileSheetEditorView::clickSelect(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept {
|
void TileSheetEditorView::clickSelect(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept {
|
||||||
auto const pt = clickPoint(paneSize, clickPos);
|
auto const pt = clickPoint(paneSize, clickPos);
|
||||||
m_model.select(pt);
|
m_model.select(pt);
|
||||||
@ -90,6 +96,7 @@ void TileSheetEditorView::releaseMouseButton(TileSheetTool tool) noexcept {
|
|||||||
switch (tool) {
|
switch (tool) {
|
||||||
case TileSheetTool::Draw:
|
case TileSheetTool::Draw:
|
||||||
case TileSheetTool::Fill:
|
case TileSheetTool::Fill:
|
||||||
|
case TileSheetTool::Line:
|
||||||
m_model.endDrawCommand();
|
m_model.endDrawCommand();
|
||||||
break;
|
break;
|
||||||
case TileSheetTool::Select:
|
case TileSheetTool::Select:
|
||||||
|
@ -23,6 +23,7 @@ enum class TileSheetTool {
|
|||||||
Select,
|
Select,
|
||||||
Draw,
|
Draw,
|
||||||
Fill,
|
Fill,
|
||||||
|
Line,
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
@ -34,6 +35,8 @@ constexpr auto toString(TileSheetTool t) noexcept {
|
|||||||
return "Draw";
|
return "Draw";
|
||||||
case TileSheetTool::Fill:
|
case TileSheetTool::Fill:
|
||||||
return "Fill";
|
return "Fill";
|
||||||
|
case TileSheetTool::Line:
|
||||||
|
return "Line";
|
||||||
case TileSheetTool::None:
|
case TileSheetTool::None:
|
||||||
return "None";
|
return "None";
|
||||||
}
|
}
|
||||||
@ -53,7 +56,7 @@ class TileSheetEditorView: public ox::SignalHandler {
|
|||||||
std::size_t m_palIdx = 0;
|
std::size_t m_palIdx = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TileSheetEditorView(studio::StudioContext &sctx, ox::StringView path, studio::UndoStack &undoStack);
|
TileSheetEditorView(studio::StudioContext &sctx, ox::StringViewCR path, studio::UndoStack &undoStack);
|
||||||
|
|
||||||
~TileSheetEditorView() override = default;
|
~TileSheetEditorView() override = default;
|
||||||
|
|
||||||
@ -65,6 +68,8 @@ class TileSheetEditorView: public ox::SignalHandler {
|
|||||||
|
|
||||||
void clickDraw(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
|
void clickDraw(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
|
||||||
|
|
||||||
|
void clickLine(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
|
||||||
|
|
||||||
void clickSelect(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
|
void clickSelect(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
|
||||||
|
|
||||||
void clickFill(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
|
void clickFill(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user