[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.
|
||||
*/
|
||||
|
||||
#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) {
|
||||
|
@ -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;
|
||||
|
||||
@ -52,4 +54,4 @@ class DrawCommand: public TileSheetCommand {
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user