[nostalgia/core/studio/tilesheet] Add line drawing tool
All checks were successful
Build / build (push) Successful in 3m36s

This commit is contained in:
Gary Talent 2025-02-01 14:14:09 -06:00
parent b5954f15c5
commit 5351e9aa0a
7 changed files with 144 additions and 12 deletions

View File

@ -2,10 +2,64 @@
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <cmath>
#include <iostream>
#include "drawcommand.hpp"
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(
TileSheet &img,
TileSheet::SubSheetIdx subSheetIdx,
@ -21,7 +75,7 @@ DrawCommand::DrawCommand(
DrawCommand::DrawCommand(
TileSheet &img,
TileSheet::SubSheetIdx subSheetIdx,
ox::Vector<std::size_t> const&idxList,
ox::SpanView<std::size_t> const&idxList,
int palIdx) noexcept:
m_img(img),
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);
if (m_changes.back().value->idx != idx && getPixel(subsheet, idx) != m_palIdx) {
// 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;
});
if (existing == m_changes.cend()) {
@ -48,7 +103,7 @@ bool DrawCommand::append(std::size_t idx) noexcept {
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;
for (auto idx : idxList) {
out = append(idx) || out;
@ -56,6 +111,25 @@ bool DrawCommand::append(ox::Vector<std::size_t> const&idxList) noexcept {
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 {
auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
for (auto const&c : m_changes) {

View File

@ -33,12 +33,14 @@ class DrawCommand: public TileSheetCommand {
DrawCommand(
TileSheet &img,
TileSheet::SubSheetIdx subSheetIdx,
ox::Vector<std::size_t> const&idxList,
ox::SpanView<std::size_t> const&idxList,
int palIdx) 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;

View File

@ -152,6 +152,12 @@ void TileSheetEditorImGui::keyStateChanged(turbine::Key const key, bool const do
setCutEnabled(false);
setPasteEnabled(false);
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) {
m_tool = TileSheetTool::Select;
setCopyEnabled(true);
@ -214,7 +220,7 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept {
ImGui::BeginChild("Controls", {s_palViewWidth - 8, paneSize.y}, true);
{
auto const controlsSize = ImGui::GetContentRegionAvail();
ImGui::BeginChild("ToolBox", {168, 32}, true);
ImGui::BeginChild("ToolBox", {0, 32}, true);
{
auto const btnSz = ImVec2{45, 14};
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_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::SameLine();
ImGui::BeginChild("OperationsBox", {0, 32}, true);
{
auto constexpr btnSz = ImVec2{55, 16};
@ -448,6 +461,9 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
case TileSheetTool::Fill:
m_view.clickFill(fbSize, clickPos(winPos, mousePos));
break;
case TileSheetTool::Line:
m_view.clickLine(fbSize, clickPos(winPos, mousePos));
break;
case TileSheetTool::Select:
m_view.clickSelect(fbSize, clickPos(winPos, mousePos));
break;

View File

@ -140,11 +140,15 @@ size_t TileSheetEditorModel::palettePage() const noexcept {
return m_palettePage;
}
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);
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_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 {
m_ongoingDrawCommand = nullptr;
}

View File

@ -34,6 +34,8 @@ class TileSheetEditorModel: public ox::SignalHandler {
class DrawCommand *m_ongoingDrawCommand = nullptr;
studio::SelectionTracker m_selTracker;
ox::Optional<studio::Selection> m_selection;
ox::Point m_lineStartPt;
ox::Point m_lastDrawUpdatePt;
bool m_updated = false;
public:
@ -71,6 +73,8 @@ class TileSheetEditorModel: public ox::SignalHandler {
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 addSubsheet(TileSheet::SubSheetIdx const&parentIdx) noexcept;

View File

@ -11,7 +11,8 @@
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_pixelsDrawer(m_model) {
glBindVertexArray(0);
@ -76,6 +77,11 @@ void TileSheetEditorView::clickDraw(ox::Vec2 const&paneSize, ox::Vec2 const&clic
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 {
auto const pt = clickPoint(paneSize, clickPos);
m_model.select(pt);
@ -90,6 +96,7 @@ void TileSheetEditorView::releaseMouseButton(TileSheetTool tool) noexcept {
switch (tool) {
case TileSheetTool::Draw:
case TileSheetTool::Fill:
case TileSheetTool::Line:
m_model.endDrawCommand();
break;
case TileSheetTool::Select:

View File

@ -23,6 +23,7 @@ enum class TileSheetTool {
Select,
Draw,
Fill,
Line,
};
[[nodiscard]]
@ -34,6 +35,8 @@ constexpr auto toString(TileSheetTool t) noexcept {
return "Draw";
case TileSheetTool::Fill:
return "Fill";
case TileSheetTool::Line:
return "Line";
case TileSheetTool::None:
return "None";
}
@ -53,7 +56,7 @@ class TileSheetEditorView: public ox::SignalHandler {
std::size_t m_palIdx = 0;
public:
TileSheetEditorView(studio::StudioContext &sctx, ox::StringView path, studio::UndoStack &undoStack);
TileSheetEditorView(studio::StudioContext &sctx, ox::StringViewCR path, studio::UndoStack &undoStack);
~TileSheetEditorView() override = default;
@ -65,6 +68,8 @@ class TileSheetEditorView: public ox::SignalHandler {
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 clickFill(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;