Fleshing out code generally.

This commit is contained in:
2025-05-20 03:26:29 -05:00
parent 5c18aa9a05
commit 8daabe4259
10 changed files with 657 additions and 316 deletions

View File

@@ -20,12 +20,12 @@ file(GLOB_RECURSE EDITOR_SRC "src/*.cpp")
include_directories("include")
CPMAddPackage(NAME mcolor
URL https://git.redacted.cc/maxine/mcolor/archive/Prerelease-7.3.zip)
CPMAddPackage(NAME jlog
URL https://git.redacted.cc/josh/jlog/archive/Prerelease-18.zip)
CPMAddPackage(NAME mcolor
URL https://git.redacted.cc/maxine/mcolor/archive/Prerelease-7.2.zip)
CPMAddPackage(NAME Event
URL https://git.redacted.cc/josh/Event/archive/Release-12.zip)
@@ -51,7 +51,8 @@ if (UNIX)
endif()
if (WIN32)
ADD_LIBRARY(Editor2D STATIC ${EDITOR_SRC})
ADD_LIBRARY(Editor2D STATIC ${EDITOR_SRC}
include/Preferences.hpp)
endif()
set_target_properties(Editor2D PROPERTIES LINKER_LANGUAGE CXX)

View File

@@ -6,4 +6,4 @@ protected:
private:
}
};

View File

@@ -16,10 +16,44 @@
using namespace ReWindow;
using namespace JUI::UDimLiterals;
struct EditorCamera {
Vector2 translation;
float rotation;
float scale;
#include <EditorCamera.hpp>
class Action {
public:
std::string label;
};
class SetTileAction: public Action {
public:
int tile_id;
int prior_tile_id;
Vector2i cell;
SetTileAction(const Vector2i& cell, int tileID, int priorTileID);
};
class BucketFillAction: public Action {
public:
int tile_id;
Vector2i cell;
std::vector<SetTileAction> effected_tiles;
BucketFillAction();
};
class UndoAction : public Action {};
class RedoAction : public Action {};
class EditHistory {
public:
void Undo(int index = 0);
void Redo(int index = 0);
std::vector<Action*> actions;
};
class EditHistoryLogWindow : public JUI::Window {
public:
};
class EditorApp : public OpenGLWindow {
@@ -39,6 +73,11 @@ public:
Color4 bg_color = Colors::Black;
bool show_cell_pointer_outline = true;
Color4 cell_pointer_outline_color = Colors::Blues::LightSteelBlue;
float cell_pointer_outline_width = 1.f;
int grid_pixel_width = 16;
int grid_pixel_height = 16;
@@ -61,336 +100,75 @@ public:
std::vector<Quad> quads;
void PopulateQuads() {
quads.reserve(tileset_width*tileset_height);
for (int i = 0; i < tileset_width*tileset_height; i++) {
Vector2i cell = IndexToCell(i, tileset_width);
quads[i] = Quad{cell.x*grid_pixel_width, cell.y*grid_pixel_height, grid_pixel_width, grid_pixel_height};
}
}
/// Fill the tile-id lookup table with their respective partial-sprite bounding boxes.
void PopulateQuads();
/// This provides a mapping from a 1D flat array to a 2D grid.
/// @return The {x,y} coordinate of the grid-cell that maps to the given index.
Vector2i IndexToCell(int index, int width);
Vector2i IndexToCell(int index, int width) {
int x = index % width;
int y = index / width;
return {x, y};
}
/// This provides a mapping from a 2D grid to a 1D flat array.
/// @return The {x,y} coordinate of the grid-cell that maps to the given index.
int CellToIndex(Vector2i cell, int width);
int CellToIndex(Vector2i cell, int width) {
return cell.y*width + cell.x;
}
EditorApp();
EditorApp() : OpenGLWindow("Editor App", 1776, 1000, GL_VER_MAJOR, GL_VER_MINOR) {
camera.rotation = 0.f;
camera.translation = {0.f, 0.f};
camera.scale = 1.f;
}
/// Loads the test.lvl file into the editor.
/// @note Placeholder until FileDialogs are added.
void LoadTestFile();
void LoadTestFile() {
std::ifstream input;
input.open("test.lvl", std::ios::binary | std::ios::in);
input.seekg(0, std::ios::end);
int data_length = input.tellg();
input.seekg(0, std::ios::beg);
/// Saves the current editor's data to test.lvl.
/// @note Placeholder until FileDialogs are added.
void SaveTestFile();
char* buffer = new char[data_length];
input.read(buffer, data_length);
input.close();
/// Loads test-data, including a tilesheet, and a binary level-data file.
/// @note Placeholder until FileDialogs are added.
/// @note Placeholder until Tileset json is added.
void LoadMisc();
auto* data = reinterpret_cast<int*>(buffer);
memcpy(grid, data, grid_rows * grid_cols * sizeof(int));
}
void SaveTestFile() {
std::ofstream output;
output.open("test.lvl", std::ios::out | std::ios::binary);
output.write(reinterpret_cast<const char*>(&grid[0][0]), grid_rows * grid_cols * sizeof(int));
output.close();
}
/// Creates a JUI widget that displays the Tileset, and lets you select a tile from it.
JUI::Window* CreateTilesetViewerWindow(JUI::Widget* parent);
void LoadMisc() {
test_tilesheet = new JGL::Texture("../megacommando.png");
/// Create all JUI elements required for this program.
void CreateWidgets();
auto texture_size = test_tilesheet->GetDimensions();
tileset_width = texture_size.x / grid_pixel_width;
tileset_height = texture_size.y / grid_pixel_height;
/// Toggles the level grid.
void ToggleGrid();
PopulateQuads();
bool Open() override;
for (int x = 0; x < grid_rows; x++) {
for (int y = 0; y < grid_cols; y++) {
grid[x][y] = -1;
}
}
Vector2i GetTilesetCellFromMouse();
if (std::filesystem::exists("test.lvl"))
LoadTestFile();
}
Vector2i GetGridCellFromMouse();
void CameraUpdate(float elapsed);
JUI::Window* CreateTilesetViewerWindow(JUI::Widget* parent) {
using namespace JUI;
void SetTile(const Vector2i& cell, int tileID);
auto* wind = new Window(parent);
wind->SetTitle("Tileset Viewer");
wind->Size(JUI::UDim2::FromPixels(test_tilesheet->GetDimensions().x, test_tilesheet->GetDimensions().y+20));
wind->SetResizable(false);
void PlaceTileBrush(const Vector2i& cell);
auto* img = new Image(wind->Content());
img->Content(test_tilesheet);
img->FitImageToParent(false);
void EraseTile(const Vector2i& cell);
auto* grid_overlay = new JGL::RenderTarget(test_tilesheet->GetDimensions());
// TODO: supplying a translucent grid_overlay_color seems to not work?
J2D::Begin(grid_overlay);
DrawGrid(AABB2D({0,0}, Vector2(test_tilesheet->GetDimensions())), grid_overlay_color);
J2D::End();
void Update(float elapsed);
auto* grid_overlay_tex = new JGL::Texture(*grid_overlay->GetTexture());
/// Draws a uniform grid of dashes lines at the boundaries of each tile.
/// @param bounds The viewport in which to draw. Grid lines will not be drawn beyond this box.
/// @param color The color with which to draw the grid lines.
void DrawGrid(const AABB2D& bounds, const Color4& color);
void DrawGrid(const Color4& color = {255,255,255,128});
auto* overlay = new JUI::Image(wind->Content());
overlay->Content(grid_overlay_tex);
overlay->ZIndex(2);
/// Draws a colored outline on the cell that the mouse-pointer is over.
void DrawCellPointerOutline();
cell_indicator = new JUI::Rect(wind->Content());
cell_indicator->Size(UDim2::FromPixels(grid_pixel_width, grid_pixel_height));
cell_indicator->BorderMode(JUI::BorderMode::Outline);
cell_indicator->BGColor(Colors::Transparent);
cell_indicator->BorderColor(Colors::Blues::CornflowerBlue);
cell_indicator->BorderWidth(2);
void DrawTiles();
return wind;
}
void Draw();
void CreateWidgets() {
scene = new JUI::Scene();
auto* topbar = new JUI::UtilityBar(scene);
auto* file = topbar->AddSubmenu("File");
file->SetFont(JGL::Fonts::Jupiteroid);
file->AddButton("New");
file->AddButton("Open");
file->AddButton("Save", [this]{SaveTestFile();});
file->AddButton("Save As");
file->AddSeparator(2_px);
file->AddButton("About");
file->AddButton("Preferences");
auto* edit = topbar->AddSubmenu("Edit");
edit->AddButton("Undo");
edit->AddButton("Redo");
edit->AddButton("Copy");
edit->AddSeparator(2_px);
edit->AddButton("Paste");
edit->AddButton("Cut Selection");
auto* view = topbar->AddSubmenu("View");
view->AddButton("Zoom In");
view->AddButton("Zoom Out");
view->AddSeparator(2_px);
view->AddButton("Toggle Grid", [this]{ToggleGrid();});
view->AddButton("Set Background Color", [this]{bg_color_tool_window->Toggle();});
auto* level = topbar->AddSubmenu("Level");
auto* layer = topbar->AddSubmenu("Layer");
layer->AddButton("New");
layer->AddButton("Open from File");
layer->AddButton("Duplicate Selected");
layer->AddButton("Delete Selected");
layer->AddButton("Edit Selected");
layer->AddButton("Export Layer");
tileset_viewer = CreateTilesetViewerWindow(scene);
bg_color_tool_window = new JUI::Window(scene);
bg_color_tool_window->Close();
bg_color_tool = new JUI::ColorPicker(bg_color_tool_window->Content());
bg_color_tool->OnColorValueChanged += [this] (Color4 new_color) mutable { bg_color = new_color; };
}
void ToggleGrid() {
grid_overlay_enabled = !grid_overlay_enabled;
}
bool Open() override {
if (!OpenGLWindow::Open()) return false;
auto size = GetSize();
auto vec_size = Vector2i(size.x, size.y);
if (!JGL::Init(vec_size, 0.f, 1.f))
return false;
JGL::Update(vec_size);
glClearColor(0.f, 0.f, 0.f, 0.f);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glDepthMask(GL_TRUE);
// TODO: Delete when we update to the next release of JGL
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // NOTE: This MUST be called for text rendering to work properly!!!
LoadMisc();
CreateWidgets();
return true;
}
Vector2i GetTilesetCellFromMouse() {
auto maus = GetMouseCoordinates();
Vector2 mouse_v2(maus.x, maus.y);
Vector2 rel = tileset_viewer->Content()->GetAbsolutePosition();
Vector2 rel_mouse = mouse_v2 - rel;
return Vector2i(
Math::Floor(rel_mouse.x/grid_pixel_width),
Math::Floor(rel_mouse.y/grid_pixel_height));
}
Vector2i GetGridCellFromMouse() {
auto maus = GetMouseCoordinates();
Vector2 mouse_v2(maus.x, maus.y);
return Vector2i(
Math::Floor(mouse_v2.x/grid_pixel_width),
Math::Floor(mouse_v2.y/grid_pixel_height));
}
void CameraUpdate(float elapsed) {
float move_rate = 10;
float zoom_rate = 0.05f;
if (IsKeyDown(Keys::LeftArrow))
camera.translation.x -= move_rate*elapsed;
if (IsKeyDown(Keys::RightArrow))
camera.translation.x += move_rate*elapsed;
if (IsKeyDown(Keys::UpArrow))
camera.translation.y -= move_rate*elapsed;
if (IsKeyDown(Keys::DownArrow))
camera.translation.y += move_rate*elapsed;
if (IsKeyDown(Keys::Minus))
camera.scale -= zoom_rate*elapsed;
if (IsKeyDown(Keys::Equals))
camera.scale += zoom_rate*elapsed;
}
void Update(float elapsed) {
CameraUpdate(elapsed);
auto size = GetSize();
Vector2i vSize = Vector2i(size.x, size.y);
JGL::Update(vSize);
scene->SetViewportSize(Vector2(vSize));
scene->Update(elapsed);
if (tileset_viewer->IsMouseInside()) {
if (tileset_viewer->Content()->IsMouseInside()) {
cell_indicator->Visible(true);
Vector2 rel_mouse = Vector2(GetTilesetCellFromMouse());
rel_mouse.x *= grid_pixel_width;
rel_mouse.y *= grid_pixel_height;
cell_indicator->Position(JUI::UDim2::FromPixels(rel_mouse.x, rel_mouse.y));
}
} else {
cell_indicator->Visible(false);
if (IsMouseButtonDown(MouseButtons::Left)) {
Vector2i grid_cell = GetGridCellFromMouse();
grid[grid_cell.x][grid_cell.y] = selected_quad;
}
if (IsMouseButtonDown(MouseButtons::Right)) {
Vector2i grid_cell = GetGridCellFromMouse();
grid[grid_cell.x][grid_cell.y] = -1;
}
}
}
void DrawGrid(const AABB2D& bounds, const Color4& color) {
Vector2 viewport_topleft = bounds.minPoint;
Vector2 viewport_bottomright = bounds.maxPoint;
int nearest_grid_left = Math::Floor(viewport_topleft.x / grid_pixel_width);
int nearest_grid_right = Math::Floor(viewport_bottomright.x / grid_pixel_width);
for (int x = nearest_grid_left; x <= nearest_grid_right; x++) {
auto top = Vector2(x * grid_pixel_width, viewport_topleft.y);
auto bottom = Vector2(x * grid_pixel_width, viewport_bottomright.y);
JGL::J2D::DrawDashedLine(color, top, bottom, 4, 4, 1 / bounds.Width());
}
int nearest_grid_top = Math::Floor(viewport_topleft.y / grid_pixel_height);
int nearest_grid_bottom = Math::Floor(viewport_bottomright.y / grid_pixel_height);
for (int y = nearest_grid_top; y <= nearest_grid_bottom; y++) {
auto left = Vector2(viewport_topleft.x, y * grid_pixel_height);
auto right = Vector2(viewport_bottomright.x, y * grid_pixel_height);
JGL::J2D::DrawDashedLine(color, left, right, 4, 4, 1 / bounds.Height());
}
}
void DrawGrid(const Color4& color = {255,255,255,128}) {
float sw = GetWidth();
float sh = GetHeight();
DrawGrid({{0,0}, {sw, sh}}, color);
}
void Draw() {
glClearColor(bg_color.RN(), bg_color.GN(), bg_color.BN(), 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
J2D::Begin();
glPushMatrix();
glTranslatef(-camera.translation.x, -camera.translation.y, 0);
glRotatef(camera.rotation, 0, 0, 1);
glScalef(camera.scale, camera.scale, 1);
if (grid_overlay_enabled)
DrawGrid(grid_overlay_color);
//J2D::DrawSprite(test_tilesheet, {32, 32}, 0, Vector2::Zero, Vector2::One, Colors::White);
for (int gx = 0; gx < grid_rows; gx++) {
for (int gy = 0; gy < grid_cols; gy++) {
auto quad_idx = grid[gx][gy];
if (quad_idx < 0)
continue;
auto quad = quads[quad_idx];
Vector2 pos(gx*grid_pixel_width, gy*grid_pixel_height);
J2D::DrawPartialSprite(test_tilesheet, pos,
Vector2(quad.x, quad.y), Vector2(quad.w, quad.h));
}
}
glPopMatrix();
J2D::End();
scene->Draw();
}
void OnClosing() override {
SaveTestFile();
}
// TODO: Closing the app nominally doesn't work on Linux.
void OnClosing() override;
void OnRefresh(float elapsed) override {
Update(elapsed);
@@ -430,7 +208,6 @@ public:
Vector2 mposv2(e.Position.x, e.Position.y);
if (scene->ObserveMouseMovement(mposv2)) return;
}
void OnKeyDown(const KeyDownEvent &e) override {

72
include/EditorCamera.hpp Normal file
View File

@@ -0,0 +1,72 @@
#pragma once
#include <J3ML/LinearAlgebra.hpp>
#include <Utils.hpp>
struct EditorCamera {
Lerped<Vector2> translation;
Lerped<float> rotation;
Lerped<float> scale;
Vector2 origin{0,0};
Vector2 screenSize{1024,768};
[[nodiscard]] Matrix4x4 CurrentTranslationMatrix() const;
[[nodiscard]] Matrix4x4 GoalTranslationMatrix() const;
Matrix4x4 CurrentRotationMatrix() const;
Matrix4x4 GoalRotationMatrix() const;
Matrix4x4 CurrentScaleMatrix() const;
Matrix4x4 GoalScaleMatrix() const
{
return Matrix4x4::Scale({scale.goal, scale.goal, 1});
}
Matrix4x4 CurrentViewMatrix() const
{
return CurrentTranslationMatrix() * CurrentScaleMatrix();
}
Matrix4x4 GoalViewMatrix() const
{
return GoalTranslationMatrix() * GoalScaleMatrix();
}
void DefaultState()
{
this->rotation.goal = 0.f;
this->rotation.current = 0.f;
this->rotation.rate = 5.f;
this->translation.goal = {0.f, 0.f};
this->translation.current = {0.f, 0.f};
this->translation.rate = 5.f;
this->scale.goal = 1.f;
this->scale.current = 1.f;
this->scale.rate = 5.f;
}
Vector2 ScreenToCameraSpace(const Vector2& ssv) const {
return {
(ssv.x + translation.current.x) / scale.current,
(ssv.y + translation.current.y) / scale.current
};
}
Vector2 CameraToScreenSpace(const Vector2& csv) const{
return {
(csv.x * scale.current) - translation.current.x,
(csv.y * scale.current) - translation.current.y
};
}
void Update(float elapsed)
{
translation.Step(elapsed);
rotation.Step(elapsed);
scale.Step(elapsed);
}
};

View File

@@ -1,5 +1,7 @@
#pragma once
#include <string>
#include <vector>
#include <TileLayer.hpp>
class Level {
public:
@@ -9,6 +11,8 @@ public:
std::vector<std::string> tags;
std::vector<TileLayer> tile_layers;
protected:
private:
};

29
include/Preferences.hpp Normal file
View File

@@ -0,0 +1,29 @@
#pragma once
#include <JJX/JSON.hpp>
#include <Color4.hpp>
using namespace JJX;
class Preferences {
public:
explicit Preferences(const json::value& json)
{
}
json::value Serialize() const
{
json::value json;
return json;
}
public:
Color4 GridColor() const;
bool ShowBackgroundGrid() const;
bool ShowForegroundGrid() const;
bool AutoSave() const;
};

View File

@@ -1,7 +1,9 @@
#pragma once
struct TileCell {
int ID;
bool flipH;
bool flipV;
};
class TileLayer {
@@ -11,6 +13,8 @@ public:
int cell_width;
int cell_height;
TileCell cells[][];
protected:
private:
};

17
include/Utils.hpp Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
#include <J3ML/LinearAlgebra.hpp>
template <typename T>
struct Lerped
{
T goal;
T current;
float rate;
void Step(float elapsed);
};
template <> inline void Lerped<float>::Step(float elapsed) { current = Math::Lerp(current, goal, rate*elapsed);}
template <> inline void Lerped<Vector2>::Step(float elapsed) { current = Vector2::Lerp(current, goal, rate*elapsed);}

View File

@@ -1 +1,410 @@
#include <EditorApp.hpp>
void EditorApp::PopulateQuads()
{
quads.reserve(tileset_width*tileset_height);
for (int i = 0; i < tileset_width*tileset_height; i++) {
Vector2i cell = IndexToCell(i, tileset_width);
quads.push_back(Quad{cell.x*grid_pixel_width, cell.y*grid_pixel_height, grid_pixel_width, grid_pixel_height});
}
}
Vector2i EditorApp::IndexToCell(int index, int width)
{
int x = index % width;
int y = index / width;
return {x, y};
}
int EditorApp::CellToIndex(Vector2i cell, int width)
{
return cell.y*width + cell.x;
}
EditorApp::EditorApp(): OpenGLWindow("Editor App", 1776, 1000, GL_VER_MAJOR, GL_VER_MINOR)
{
camera.DefaultState();
}
void EditorApp::LoadTestFile()
{
std::ifstream input;
input.open("test.lvl", std::ios::binary | std::ios::in);
input.seekg(0, std::ios::end);
int data_length = input.tellg();
input.seekg(0, std::ios::beg);
char* buffer = new char[data_length];
input.read(buffer, data_length);
input.close();
auto* data = reinterpret_cast<int*>(buffer);
memcpy(grid, data, grid_rows * grid_cols * sizeof(int));
}
void EditorApp::SaveTestFile()
{
std::ofstream output;
output.open("test.lvl", std::ios::out | std::ios::binary);
output.write(reinterpret_cast<const char*>(&grid[0][0]), grid_rows * grid_cols * sizeof(int));
output.close();
}
void EditorApp::LoadMisc()
{
test_tilesheet = new JGL::Texture("../megacommando.png");
auto texture_size = test_tilesheet->GetDimensions();
tileset_width = texture_size.x / grid_pixel_width;
tileset_height = texture_size.y / grid_pixel_height;
PopulateQuads();
for (int x = 0; x < grid_rows; x++) {
for (int y = 0; y < grid_cols; y++) {
grid[x][y] = -1;
}
}
if (std::filesystem::exists("test.lvl"))
LoadTestFile();
}
JUI::Window* EditorApp::CreateTilesetViewerWindow(JUI::Widget* parent)
{
using namespace JUI;
auto* wind = new Window(parent);
wind->SetTitle("Tileset Viewer");
wind->Size(JUI::UDim2::FromPixels(test_tilesheet->GetDimensions().x, test_tilesheet->GetDimensions().y+20));
wind->SetResizable(false);
auto* img = new Image(wind->Content());
img->Content(test_tilesheet);
img->FitImageToParent(false);
auto* grid_overlay = new JGL::RenderTarget(test_tilesheet->GetDimensions());
// TODO: supplying a translucent grid_overlay_color seems to not work?
J2D::Begin(grid_overlay);
DrawGrid(AABB2D({0,0}, Vector2(test_tilesheet->GetDimensions())), grid_overlay_color);
J2D::End();
auto* grid_overlay_tex = new JGL::Texture(*grid_overlay->GetTexture());
auto* overlay = new JUI::Image(wind->Content());
overlay->Content(grid_overlay_tex);
overlay->ZIndex(2);
cell_indicator = new JUI::Rect(wind->Content());
cell_indicator->Size(UDim2::FromPixels(grid_pixel_width, grid_pixel_height));
cell_indicator->BorderMode(JUI::BorderMode::Outline);
cell_indicator->BGColor(Colors::Transparent);
cell_indicator->BorderColor(Colors::Blues::CornflowerBlue);
cell_indicator->BorderWidth(2);
return wind;
}
void EditorApp::CreateWidgets()
{
scene = new JUI::Scene();
auto* topbar = new JUI::UtilityBar(scene);
auto* file = topbar->AddSubmenu("File");
file->SetFont(JGL::Fonts::Jupiteroid);
file->AddButton("New");
file->AddButton("Open");
file->AddButton("Save", [this]{SaveTestFile();});
file->AddButton("Save As");
file->AddSeparator(2_px);
file->AddButton("About");
file->AddButton("Preferences");
auto* edit = topbar->AddSubmenu("Edit");
edit->AddButton("Undo");
edit->AddButton("Redo");
edit->AddButton("Copy");
edit->AddSeparator(2_px);
edit->AddButton("Paste");
edit->AddButton("Cut Selection");
auto* view = topbar->AddSubmenu("View");
view->AddButton("Zoom In");
view->AddButton("Zoom Out");
view->AddSeparator(2_px);
view->AddButton("Toggle Grid", [this]{ToggleGrid();});
view->AddButton("Set Background Color", [this]{bg_color_tool_window->Toggle();});
auto* level = topbar->AddSubmenu("Level");
auto* layer = topbar->AddSubmenu("Layer");
layer->AddButton("New");
layer->AddButton("Open from File");
layer->AddButton("Duplicate Selected");
layer->AddButton("Delete Selected");
layer->AddButton("Edit Selected");
layer->AddButton("Export Layer");
tileset_viewer = CreateTilesetViewerWindow(scene);
bg_color_tool_window = new JUI::Window(scene);
bg_color_tool_window->Close();
bg_color_tool = new JUI::ColorPicker(bg_color_tool_window->Content());
bg_color_tool->OnColorValueChanged += [this] (Color4 new_color) mutable { bg_color = new_color; };
}
void EditorApp::ToggleGrid()
{
grid_overlay_enabled = !grid_overlay_enabled;
}
bool EditorApp::Open()
{
if (!OpenGLWindow::Open()) return false;
auto size = GetSize();
auto vec_size = Vector2i(size.x, size.y);
if (!JGL::Init(vec_size, 0.f, 1.f))
return false;
JGL::Update(vec_size);
glClearColor(0.f, 0.f, 0.f, 0.f);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glDepthMask(GL_TRUE);
// TODO: Delete when we update to the next release of JGL
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // NOTE: This MUST be called for text rendering to work properly!!!
LoadMisc();
CreateWidgets();
return true;
}
Vector2i EditorApp::GetTilesetCellFromMouse()
{
auto maus = GetMouseCoordinates();
Vector2 mouse_v2(maus.x, maus.y);
Vector2 rel = tileset_viewer->Content()->GetAbsolutePosition();
Vector2 rel_mouse = mouse_v2 - rel;
return Vector2i(
Math::Floor(rel_mouse.x/grid_pixel_width),
Math::Floor(rel_mouse.y/grid_pixel_height));
}
Vector2i EditorApp::GetGridCellFromMouse()
{
auto maus = GetMouseCoordinates();
Vector2 mouse_v2(maus.x, maus.y);
mouse_v2 = camera.ScreenToCameraSpace(mouse_v2);
return Vector2i(
Math::Floor(mouse_v2.x/grid_pixel_width),
Math::Floor(mouse_v2.y/grid_pixel_height));
}
void EditorApp::CameraUpdate(float elapsed)
{
float move_rate = 40;
float zoom_rate = 0.1f;
if (IsKeyDown(Keys::LeftArrow))
camera.translation.goal.x -= move_rate*elapsed;
if (IsKeyDown(Keys::RightArrow))
camera.translation.goal.x += move_rate*elapsed;
if (IsKeyDown(Keys::UpArrow))
camera.translation.goal.y -= move_rate*elapsed;
if (IsKeyDown(Keys::DownArrow))
camera.translation.goal.y += move_rate*elapsed;
if (IsKeyDown(Keys::Minus))
camera.scale.goal -= zoom_rate*elapsed;
if (IsKeyDown(Keys::Equals))
camera.scale.goal += zoom_rate*elapsed;
camera.Update(elapsed);
}
void EditorApp::SetTile(const Vector2i& cell, int tileID)
{
// out of bounds horizontally
if (cell.x > grid_rows) return;
// out of bounds vertically
if (cell.y > grid_cols) return;
grid[cell.x][cell.y] = tileID;
}
void EditorApp::PlaceTileBrush(const Vector2i& cell)
{
// out of bounds horizontally
if (cell.x > grid_rows) return;
// out of bounds vertically
if (cell.y > grid_cols) return;
SetTile(cell, selected_quad);
}
void EditorApp::EraseTile(const Vector2i& cell)
{
// out of bounds horizontally
if (cell.x > grid_rows) return;
// out of bounds vertically
if (cell.y > grid_cols) return;
SetTile(cell, -1);
}
void EditorApp::Update(float elapsed)
{
CameraUpdate(elapsed);
auto size = GetSize();
Vector2i vSize = Vector2i(size.x, size.y);
camera.screenSize.x = vSize.x;
camera.screenSize.y = vSize.y;
JGL::Update(vSize);
scene->SetViewportSize(Vector2(vSize));
scene->Update(elapsed);
if (tileset_viewer->IsMouseInside()) {
if (tileset_viewer->Content()->IsMouseInside()) {
cell_indicator->Visible(true);
Vector2 rel_mouse = Vector2(GetTilesetCellFromMouse());
rel_mouse.x *= grid_pixel_width;
rel_mouse.y *= grid_pixel_height;
cell_indicator->Position(JUI::UDim2::FromPixels(rel_mouse.x, rel_mouse.y));
}
} else {
cell_indicator->Visible(false);
if (IsMouseButtonDown(MouseButtons::Left))
PlaceTileBrush(GetGridCellFromMouse());
if (IsMouseButtonDown(MouseButtons::Right))
EraseTile(GetGridCellFromMouse());
}
}
void EditorApp::DrawGrid(const AABB2D& bounds, const Color4& color)
{
Vector2 viewport_topleft = bounds.minPoint;
Vector2 viewport_bottomright = bounds.maxPoint;
int nearest_grid_left = Math::Floor(viewport_topleft.x / grid_pixel_width);
int nearest_grid_right = Math::Floor(viewport_bottomright.x / grid_pixel_width);
for (int x = nearest_grid_left; x <= nearest_grid_right; x++) {
auto top = Vector2(x * grid_pixel_width, viewport_topleft.y);
auto bottom = Vector2(x * grid_pixel_width, viewport_bottomright.y);
JGL::J2D::DrawDashedLine(color, top, bottom, 4, 4, 1 / bounds.Width());
}
int nearest_grid_top = Math::Floor(viewport_topleft.y / grid_pixel_height);
int nearest_grid_bottom = Math::Floor(viewport_bottomright.y / grid_pixel_height);
for (int y = nearest_grid_top; y <= nearest_grid_bottom; y++) {
auto left = Vector2(viewport_topleft.x, y * grid_pixel_height);
auto right = Vector2(viewport_bottomright.x, y * grid_pixel_height);
JGL::J2D::DrawDashedLine(color, left, right, 4, 4, 1 / bounds.Height());
}
}
void EditorApp::DrawGrid(const Color4& color)
{
float sw = GetWidth();
float sh = GetHeight();
DrawGrid({{0,0}, {sw, sh}}, color);
}
void EditorApp::DrawCellPointerOutline()
{
Vector2 rel_mouse = Vector2(GetGridCellFromMouse());
rel_mouse.x *= grid_pixel_width;
rel_mouse.y *= grid_pixel_height;
J2D::OutlineRect(cell_pointer_outline_color, rel_mouse, Vector2(grid_pixel_width, grid_pixel_height), cell_pointer_outline_width);
}
void EditorApp::DrawTiles()
{
for (int gx = 0; gx < grid_rows; gx++) {
for (int gy = 0; gy < grid_cols; gy++) {
auto quad_idx = grid[gx][gy];
if (quad_idx < 0)
continue;
auto quad = quads[quad_idx];
Vector2 pos(gx*grid_pixel_width, gy*grid_pixel_height);
J2D::DrawPartialSprite(test_tilesheet, pos,
Vector2(quad.x, quad.y), Vector2(quad.w, quad.h));
}
}
}
void EditorApp::Draw()
{
glClearColor(bg_color.RN(), bg_color.GN(), bg_color.BN(), 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
J2D::Begin();
glPushMatrix();
glTranslatef(-camera.translation.current.x, -camera.translation.current.y, 0);
glRotatef(camera.rotation.current, 0, 0, 1);
glScalef(camera.scale.current, camera.scale.current, 1);
if (grid_overlay_enabled)
DrawGrid(grid_overlay_color);
DrawTiles();
if (grid_overlay_enabled)
DrawGrid(grid_overlay_color);
DrawCellPointerOutline();
glPopMatrix();
auto maus = GetMouseCoordinates();
Vector2 mouse_v2(maus.x, maus.y);
auto bruhbruh = camera.CameraToScreenSpace(camera.ScreenToCameraSpace(mouse_v2));
J2D::DrawPoint(Colors::White, bruhbruh, 4);
J2D::End();
scene->Draw();
}
void EditorApp::OnClosing()
{
SaveTestFile();
}

28
src/EditorCamera.cpp Normal file
View File

@@ -0,0 +1,28 @@
#include <EditorCamera.hpp>
Matrix4x4 EditorCamera::CurrentTranslationMatrix() const
{
return Matrix4x4::FromTranslation({translation.current.x, translation.current.y, 0});
}
Matrix4x4 EditorCamera::GoalTranslationMatrix() const
{
return Matrix4x4::FromTranslation({translation.goal.x, translation.goal.y, 0});
}
Matrix4x4 EditorCamera::CurrentRotationMatrix() const
{
// TODO: Which axis?
return Matrix4x4::RotateX(rotation.current);
}
Matrix4x4 EditorCamera::GoalRotationMatrix() const
{
// TODO: Which axis?
return Matrix4x4::RotateX(rotation.goal);
}
Matrix4x4 EditorCamera::CurrentScaleMatrix() const
{
return Matrix4x4::Scale({scale.current, scale.current, 1});
}