Fleshing out code generally.
This commit is contained in:
@@ -1 +1,410 @@
|
||||
#include <EditorApp.hpp>
|
||||
#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
28
src/EditorCamera.cpp
Normal 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});
|
||||
}
|
Reference in New Issue
Block a user