Refactoring **everything**

This commit is contained in:
2025-05-29 02:56:51 -05:00
parent 391dcd32ad
commit 74b1d0d1fc
26 changed files with 629 additions and 331 deletions

View File

@@ -33,7 +33,7 @@ CPMAddPackage(NAME Event
URL https://git.redacted.cc/josh/Event/archive/Release-12.zip)
CPMAddPackage(NAME jjx
URL https://git.redacted.cc/josh/jjx/archive/Release-1.1.zip)
URL https://git.redacted.cc/josh/jjx/archive/Release-1.2.zip)
CPMAddPackage(NAME J3ML
URL https://git.redacted.cc/josh/j3ml/archive/3.4.5.zip)
@@ -53,16 +53,12 @@ if (UNIX)
endif()
if (WIN32)
ADD_LIBRARY(Editor2D STATIC ${EDITOR_SRC}
include/Preferences.hpp
src/Utils.cpp
include/NewMapDialog.hpp
src/Tileset.cpp
src/Layer.cpp)
ADD_LIBRARY(Editor2D STATIC ${EDITOR_SRC})
endif()
set_target_properties(Editor2D PROPERTIES LINKER_LANGUAGE CXX)
# TODO: Separate dependency packages such that only bare-minimum required is compiled with the just the lib.
target_include_directories(Editor2D PUBLIC
${Event_SOURCE_DIR}/include
${J3ML_SOURCE_DIR}/include

8
include/App/Editor.hpp Normal file
View File

@@ -0,0 +1,8 @@
//
// Created by josh on 5/29/2025.
//
#ifndef EDITOR_HPP
#define EDITOR_HPP
#endif //EDITOR_HPP

View File

@@ -13,7 +13,11 @@
#include "NewMapDialog.hpp"
#include "JUI/Widgets/ColorPicker.hpp"
#include "Preferences.hpp"
#include <Level.hpp>
#include <Data/Level.hpp>
#include <Data/Tileset.hpp>
#include <JUI/Widgets/Checkbox.hpp>
#include <App/LayerView.hpp>
#include <App/TilesetView.hpp>
#define GL_VER_MAJOR 2
#define GL_VER_MINOR 1
@@ -21,7 +25,10 @@
using namespace ReWindow;
using namespace JUI::UDimLiterals;
#include <EditorCamera.hpp>
#include <App/EditorCamera.hpp>
// TODO: Support loading an entity_prefab.json file which defines template entities to copy.
// TODO: Support sprites for these?
class Action {
public:
@@ -61,85 +68,55 @@ class EditHistoryLogWindow : public JUI::Window {
public:
};
class LayerView : public JUI::Window
{
public:
LayerView() : JUI::Window()
{
this->SetTitle("Layers");
Name("LayerView");
}
explicit LayerView(Widget* parent) : LayerView()
{
Parent(parent);
}
};
class EditorApp : public OpenGLWindow {
public:
#pragma region GUI Elements
JUI::Scene* scene = nullptr;
JUI::CommandLine* console = nullptr;
JUI::Window* tileset_viewer = nullptr;
JUI::Rect* cell_indicator = nullptr;
NewMapDialog* nmd = nullptr;
LayerView* layer_view = nullptr;
JUI::Window* bg_color_tool_window = nullptr;
JUI::ColorPicker* bg_color_tool = nullptr;
#pragma endregion
#pragma region Map Data
Level* loaded_level;
Tileset* loaded_tileset;
JGL::Texture* loaded_tilesheet;
#pragma endregion
EditorCamera camera;
JUI::Scene* scene;
JUI::CommandLine* console;
JUI::Window* tileset_viewer;
JUI::Rect* cell_indicator;
JGL::Texture* test_tilesheet;
LayerView* layer_view;
NewMapDialog* nmd = nullptr;
Level loaded_level;
Preferences preferences;
JUI::Window* bg_color_tool_window = nullptr;
JUI::ColorPicker* bg_color_tool = nullptr;
int focus_layer_index = 0;
Layer* GetLayer(int index)
{
return loaded_level->layers[index];
}
Layer* GetFocusLayer()
{
return loaded_level->layers[focus_layer_index];
}
bool grid_overlay_enabled = true;
Color4 grid_overlay_color = {255, 255, 255, 64};
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;
int tileset_width = 420;
int tileset_height = 420;
int grid_rows = 128;
int grid_cols = 128;
int** grid;
void InitGrid()
{
grid = new int*[grid_rows];
for (int i = 0; i < grid_rows; i++)
grid[i] = new int[grid_cols];
for (int x = 0; x < grid_rows; x++) {
for (int y = 0; y < grid_cols; y++) {
grid[x][y] = -1;
}
}
}
void DeleteGrid()
{
for (int i = 0; i < grid_rows; i++)
delete[] grid[i];
delete[] grid;
}
int selected_quad = 0;
bool data_ready = false;
struct Quad {
int x;
int y;
@@ -147,7 +124,7 @@ public:
int h;
};
std::vector<Quad> quads;
//std::vector<Quad> quads;
/// Fill the tile-id lookup table with their respective partial-sprite bounding boxes.
void PopulateQuads();
@@ -165,7 +142,7 @@ public:
EditorApp();
~EditorApp() override
{
DeleteGrid();
//DeleteGrid();
}
void LoadPreferences();
@@ -185,6 +162,7 @@ public:
/// @note Placeholder until FileDialogs are added.
/// @note Placeholder until Tileset json is added.
void LoadMisc();
void SizeCellIndicatorToTilesetSize();
/// Creates a JUI widget that displays the Tileset, and lets you select a tile from it.
JUI::Window* CreateTilesetViewerWindow(JUI::Widget* parent);
@@ -229,6 +207,47 @@ public:
void DrawTiles();
void DrawLayer(const Layer* layer) const
{
for (int gx = 0; gx < layer->rows; gx++) {
for (int gy = 0; gy < layer->cols; gy++) {
auto quad_idx = layer->cells[gx][gy];
Vector2 pos(gx*layer->cell_width, gy*layer->cell_height);
//if ((gx+gy) % 2 == 0)
//J2D::FillRect(Colors::Purples::Fuchsia, pos, {16, 16});
if (quad_idx < 0)
continue;
auto quad = loaded_tileset->quads[quad_idx];
J2D::DrawPartialSprite(loaded_tilesheet, pos,
Vector2(quad.x, quad.y), Vector2(quad.w, quad.h));
}
}
}
void DrawLevel(const Level* level)
{
// TODO: Draw Per-Level Background Texture.
// TODO: Draw Per-Layer Background Texture.
for (const auto* layer : level->layers)
{
DrawLayer(layer);
}
// TODO: Support a handful of presets to represent entity shape types.
for (auto entity : level->entities)
{
J2D::DrawPoint(entity->overlay_color, entity->x, entity->y, 1);
J2D::DrawString(Colors::White, std::format("{} - {}", entity->type, entity->name), entity->x, entity->y, 1.f, 12);
}
}
void Draw();
// TODO: Closing the app nominally doesn't work on Linux.
@@ -259,7 +278,7 @@ public:
if (btn == JUI::MouseButton::Middle)
{
Vector2i cell = GetGridCellFromMouse();
selected_quad = grid[cell.x][cell.y];
selected_quad = loaded_level->layers[0]->cells[cell.x][cell.y];
}
}
@@ -269,7 +288,7 @@ public:
if (tileset_viewer->Content()->IsMouseInside()) {
Vector2i cell = GetTilesetCellFromMouse();
int index = CellToIndex(cell, tileset_width);
int index = CellToIndex(cell, loaded_tileset->rows);
selected_quad = index;
}

39
include/App/LayerView.hpp Normal file
View File

@@ -0,0 +1,39 @@
#pragma once
#include <JUI/Widgets/Window.hpp>
#include <JUI/Widgets/Button.hpp>
#include <JUI/Widgets/ScrollingRect.hpp>
#include <JUI/Widgets/ListLayout.hpp>
#include <JUI/Widgets/Checkbox.hpp>
#include <Data/Level.hpp>
class LayerView : public JUI::Window
{
public:
LayerView() : JUI::Window()
{
this->SetTitle("Layers");
Name("LayerView");
scroller = new JUI::ScrollingRect(this->Content());
layout = new JUI::VerticalListLayout(scroller);
}
explicit LayerView(Widget* parent) : LayerView()
{
Parent(parent);
}
void UpdateComponents(Level* level)
{
for (auto layer : level->layers)
{
//auto* rect = new JUI::Rect(layout);
//auto* vis_chk = new JUI::Checkbox(rect);
}
}
JUI::ScrollingRect* scroller = nullptr;
JUI::VerticalListLayout* layout = nullptr;
};

View File

@@ -0,0 +1,2 @@
#pragma once

61
include/Data/Entity.hpp Normal file
View File

@@ -0,0 +1,61 @@
#pragma once
#include <string>
#include <JJX/json.hpp>
#include <Color4.hpp>
using namespace JJX;
enum class EntityRepresentativeShape {
Point, AABB, Sphere, OBB
};
/// @class Entity
/// @brief Represents an object or interactive element placed on the map, not necessarily tied to the tile grid.
/// This can include plater spawn points, enemies, pickups, triggers, boxes (my favorite), or any other
/// game specific object that needs to be placed and configured in the level.
class Entity
{
public:
#pragma region json properties
std::string name;
std::string type;
float x;
float y;
float width;
float height;
float rotation;
float flip_h;
float flip_v;
int z_index;
Color4 overlay_color;
json::value metadata;
#pragma endregion
Entity();
Entity(const json::value& json)
{
Deserialize(json);
}
json::value Serialize() const
{
}
void Deserialize(const json::value& json)
{
name = json["name"].as_string();
type = json["type"].as_string();
x = json["x"].number.value();
y = json["y"].number.value();
width = json["width"].number.value_or(0);
height = json["height"].number.value_or(0);
}
};

7
include/Data/Etc.hpp Normal file
View File

@@ -0,0 +1,7 @@
#pragma once
namespace LevelFormat
{
};

5
include/Data/Format.hpp Normal file
View File

@@ -0,0 +1,5 @@
#include <Data/Entity.hpp>
#include <Data/Layer.hpp>
#include <Data/Tileset.hpp>
#include <Data/Level.hpp>
#include <Data/Tile.hpp>

View File

@@ -1,7 +1,7 @@
#pragma once
#include <string>
#include <vector>
#include <Layer.hpp>
#include <Data/Layer.hpp>
#include <JJX/JSON.hpp>
#include <Colors.hpp>
#include <Utils.hpp>
@@ -41,7 +41,10 @@ public:
int cell_width;
/// The height of the individual cells of this layer, measured in pixels.
int cell_height;
/// The horizontal parallax scrolling factor for this layer.
float parallax_x;
/// The vertical parallax scrolling factor for this layer.
float parallax_y;
bool visible;
@@ -51,7 +54,7 @@ public:
#pragma endregion
#pragma region binary data storage
int** cells;
int** cells = nullptr;
#pragma endregion
@@ -69,10 +72,7 @@ public:
/// Destructor for Layer cleans up all dynamically allocated memory for the tile grid.
~Layer()
{
DeleteGrid();
}
~Layer();
/// Constructs a layer object from the JSON contained in the file at the given path.
@@ -104,58 +104,26 @@ public:
void LoadFromDataBuffer(const int* buffer);
void WriteToDataBuffer(int* buffer)
{
//int* buffer = new int[rows*cols];
for (int x = 0; x < rows; x++)
{
for (int y = 0; y < cols; y++)
{
int index = (x*cols + y);
buffer[index] = cells[x][y];
}
}
}
void WriteToDataBuffer(int* buffer) const;
/// Writes the current tile grid data to a specified raw binary file.
/// The file path should typically be stored in the layer's JSON metadata.
/// @param path
/// @return True if the binary data was written successfully, false otherwise.
/// @see Save().
bool WriteBinaryData(const std::filesystem::path& path) const;
bool Save();
bool Load();
/// Reads tile grid data from a specified raw binary file.
/// The file path should typically be stored in the layer's JSON metadata.
/// @param path
/// @return True if the binary data was loaded successfully, false otherwise.
bool WriteBinaryData(const std::filesystem::path& path)
{
int* buffer = new int[rows*cols];
WriteToDataBuffer(buffer);
std::ofstream output;
output.open(path, std::ios::out | std::ios::binary);
output.write(reinterpret_cast<const char*>(buffer), rows * cols * sizeof(int));
output.close();
/// @see Load().
bool ReadBinaryData(const std::filesystem::path& path);
delete[] buffer;
return true;
}
bool Save() { return WriteBinaryData(binary_path); }
bool Load() { return ReadBinaryData(binary_path); }
bool ReadBinaryData(const std::filesystem::path& path)
{
std::ifstream input;
input.open(path, 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();
InitGrid();
LoadFromDataBuffer(reinterpret_cast<int*>(buffer));
delete[] buffer;
return true;
}
protected:
bool grid_allocated = false;
};

127
include/Data/Level.hpp Normal file
View File

@@ -0,0 +1,127 @@
#pragma once
#include <string>
#include <vector>
#include <Data/Layer.hpp>
#include <Data/Entity.hpp>
#include <JJX/JSON.hpp>
#include <Colors.hpp>
#include <Utils.hpp>
#include "Tileset.hpp"
using namespace JJX;
const float REDACTED_EDITOR_LIB_VERSION = 1;
/// @class Level
/// @brief Represents the complete game level, containing its metadata, tile layers, and entites.
///
/// This class serves as the primary data structure for loading, managing, and saving
/// all components that constitute the playable game map. It centralizes information
/// about the level's dimensions, graphical assets (tilesets), stuctural layers,
/// and interactive objects (entities).
class Level {
public:
/// The human-readable name of the level.
/// This is typically displayed in level selection menus or editors.
std::string name;
/// A brief description of the level's content or purpose.
/// Useful for level browsers or internal documentation.
std::string description;
/// The name or identifier of the author who created or last modified the level.
std::string author;
/// A collection of keywords or categories associated with the level.
/// Can be used for filtering, searching, or thematic grouping (e.g., "forest", "puzzle").
std::vector<std::string> tags;
/// The height of the level grid in number of tiles (rows).
// TODO: I mixed up rows and columns. I'm just going to leave it as the convention this system follows for now. -josh
int rows;
/// The width of the level grid in number of tiles (columns).
int cols;
/// A collection of pointers to the tile layers that compose the level's tile grid.
/// Each layer typically represents a distinct plane of tiles (e.g., background, foreground, collisions).
/// @warning The `Level` class is responsible for the ownership and lifecycle of these `Layer` objects
/// if they are allocated dynamically. Consider using smart pointers like `std::unique_ptr<Layer>
/// for automatic memory management,
std::vector<Layer*> layers;
/// A collection of pointers to the entities placed within the level.
/// Entities represent interactive objects, characters, spawn points, or other dynamic elements.
/// @warning Similar to `layers`, the `Level` class is responsible for the ownership and lifecycle
/// of these `Entity` objects if they are dynamically allocated. Consider `std::unique_ptr<Entity>`.
std::vector<Entity*> entities;
/// The filesystem path to the primary tileset image or definition file used by this level.
/// This path is typically relative to the level's main JSON file.
std::string tileset_path;
/// A generic JSON value for storing arbitrary, unstructured level metdata.
/// This can include game-specific settings, editor-specific flags, or any data
/// not explicitly covered by other `Level` members.
json::value metadata;
#pragma region Member Functions
/// The default constructor initializes all member variables to their default or empty states.
/// The level will have zero dimensions, no layers, no entities, and empty metadata.
Level();
/// Destructor for the Level class.
/// Responsible for deallocating all dynamically allocated `Layer` and `Entity` objects
/// pointed to by the `layers` and `entities` vectors to prevent memory leaks.
~Level();
/// Constructs a Level object by loading and parsing a level definition file from the given path.
/// This constructor typically reads a JSON file containing the level's metadata, and
/// may also initiate the loading of associated binary tile data or other assets. NOTE: Currently, we do not.
/// @param path
/// @throws std::runtime_error If the file cannot be opened, is malformed, or if associated data (like binary layers) is missing / invalid.
explicit Level(const std::filesystem::path& path);
/// Constructs a Level object by deserializing its state from a pre-existing JSON value.
/// This constructor assumes the JSON value is already loaded in memory and correct.
/// It will parse the JSON to populate the level's members, including layers and entities.
/// @param json
/// @throws json::exception If the JSON structure is malformed.
/// @see Deserialize() for the underlying parsing logic.
explicit Level(const json::value& json);
/// Deserializes the Level object's state from a provided JSON value.
/// This method takes a JSON object (typically parsed from a file) and populates
/// the current `Level` instance's member variables, including its layers and entities.
/// This is the core parsing logic for loading level data into memory.
/// @param json
/// @throws json::exception If the JSON structure is malformed.
void Deserialize(const json::value& json);
/// Serializes the current Level object's state into a JSON value.
/// This method converts all the level's metadata, layer properties, and entity data
/// into a json object, ready for saving to a file or transmitting.
/// @return a `json` value representing the complete level data.
/// @see Level::Save() for writing the serialized JSON to a file.
json::value Serialize() const;
bool Load(const std::filesystem::path& path);
bool Save(const std::filesystem::path& path);
Layer* CreateLayer(const std::string& name);
Layer* GetLayer(int index);
const Layer* GetLayer(int index) const;
Layer* FindLayer(const std::string& name);
const Layer* FindLayer(const std::string& name) const;
bool RemoveLayer(const std::string& name);
bool RemoveLayer(int index);
bool RemoveLayer(Layer* layer);
bool RemoveAllLayers();
Entity* CreateEntity(const std::string& name);
Entity* GetEntity(const std::string& name);
const Entity* GetEntity(const std::string& name) const;
bool RemoveEntity(Entity* entity);
Tile GetTileData(int gid) const;
Quad GetTileQuad(int gid) const;
#pragma endregion
protected:
private:
};

3
include/Data/Tile.hpp Normal file
View File

@@ -0,0 +1,3 @@
#pragma once

View File

@@ -79,6 +79,22 @@ public:
#pragma endregion
public:
static Tileset Template()
{
Tileset tileset;
tileset.author = "J. O'Leary";
tileset.name = "Sample Tileset";
tileset.file_path = "tileset.json";
tileset.cols = 80;
tileset.rows = 64;
tileset.texture_path = "../megacommando.png";
tileset.tex_width = 1024;
tileset.tex_height = 1280;
return tileset;
}
/// Default constructor for Tileset .
/// Initializes member variables to default or zero values.
/// @see CreateDefault().

View File

@@ -1,11 +0,0 @@
#pragma once
class Editor {
public:
protected:
private:
};

View File

@@ -1,22 +0,0 @@
#pragma once
/// @class Entity
/// @brief Represents an object or interactive element placed on the map, not necessarily tied to the tile grid.
/// This can include plater spawn points, enemies, pickups, triggers, boxes (my favorite), or any other
/// game specific object that needs to be placed and configured in the level.
class Entity
{
public:
#pragma region json properties
std::string name;
std::string type;
float x;
float y;
#pragma endregion
Entity();
Entity(const json::value& json);
};

View File

@@ -1,85 +0,0 @@
#pragma once
#include <string>
#include <vector>
#include <Layer.hpp>
#include <JJX/JSON.hpp>
#include <Colors.hpp>
#include <Utils.hpp>
using namespace JJX;
const float REDACTED_EDITOR_LIB_VERSION = 1;
class Level {
public:
std::string name;
std::string description;
std::string author;
std::vector<std::string> tags;
int rows;
int cols;
std::vector<Layer> layers;
std::string tileset_path;
json::value metadata;
Level() {}
explicit Level(const std::filesystem::path& path)
{
auto [json, err] = json::parse(read_file_contents(path));
Deserialize(json);
}
explicit Level(const json::value& json) {
Deserialize(json);
}
void Deserialize(const json::value& json)
{
name = json["name"].as_string();
description = json["description"].as_string();
author = json["author"].as_string();
tileset_path = json["tileset-path"].as_string();
auto layer_json_array = json["layers"].as_array();
for (auto& layer_json : layer_json_array)
{
layers.push_back(Layer(layer_json));
}
}
json::value Serialize() const
{
json::object data;
data["name"] = name;
data["description"] = description;
data["author"] = author;
data["tileset-path"] = tileset_path;
data["editor-version"] = REDACTED_EDITOR_LIB_VERSION;
data["map-type"] = "OrthogonalTileMap";
data["map-rows"] = (double)rows;
data["map-cols"] = (double)cols;
data["tile-width"] = 16.f;
data["tile-height"] = 16.f;
data["bgcolor"] = JsonConversions::deparse_color_to_hex(Colors::Black);
data["tags"] = JsonConversions::deparse_string_list(tags);
json::array layers;
for (auto& layer : this->layers)
{
layers.push_back(layer.Serialize());
}
data["layers"] = layers;
return data;
}
void Deserialize(const json::value& json) const {
}
protected:
private:
};

View File

@@ -2,9 +2,9 @@
// Created by dawsh on 5/12/25.
//
#include <EditorApp.hpp>
#include <App/EditorApp.hpp>
#include "ReWindow/Logger.h"
#include "cmake-build-debug/_deps/rewindow-src/include/ReWindow/Logger.h"
int main() {

3
src/App/Editor.cpp Normal file
View File

@@ -0,0 +1,3 @@
//
// Created by josh on 5/29/2025.
//

View File

@@ -1,16 +1,15 @@
#include <EditorApp.hpp>
#include <App/EditorApp.hpp>
#include <App/NewMapDialog.hpp>
#include "NewMapDialog.hpp"
#include <Tileset.hpp>
#include <Data/Format.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});
}
//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)
@@ -125,23 +124,18 @@ void EditorApp::SavePreferences()
void EditorApp::LoadTestFile()
{
if (!std::filesystem::exists("test_level"))
std::filesystem::create_directory("test_level");
LoadLevel("level.json");
//if (!std::filesystem::exists("test_level/metadata.json"))
Level test_level = Level(std::filesystem::path("level.json"));
grid_rows = test_level.layers[0].rows;
grid_cols = test_level.layers[0].cols;
//loaded_level = Level(std::filesystem::path("level.json"));
//grid_rows = loaded_level->layers[0].rows;
//grid_cols = loaded_level->layers[0].cols;
PopulateQuads();
InitGrid();
//InitGrid();
//std::string level_metadata_text = read_file_contents("test_level/metadata.json");
std::ifstream input;
/*std::ifstream input;
input.open("test.lvl", std::ios::binary | std::ios::in);
input.seekg(0, std::ios::end);
int data_length = input.tellg();
@@ -164,13 +158,13 @@ void EditorApp::LoadTestFile()
//memcpy(grid, data, grid_rows * grid_cols * sizeof(int));
delete[] buffer;
delete[] buffer;*/
}
void EditorApp::SaveTestFile()
{
int* buffer = new int[grid_rows*grid_cols];
/*int* buffer = new int[grid_rows*grid_cols];
for (int x = 0; x < grid_rows; x++)
{
for (int y = 0; y < grid_cols; y++)
@@ -185,36 +179,36 @@ void EditorApp::SaveTestFile()
output.write(reinterpret_cast<const char*>(buffer), grid_rows * grid_cols * sizeof(int));
output.close();
delete[] buffer;
delete[] buffer;*/
}
void EditorApp::LoadLevel(const std::filesystem::path& level_meta_path)
{
loaded_level = new Level(level_meta_path);
loaded_tileset = new Tileset(std::filesystem::path(loaded_level->tileset_path));
loaded_tilesheet = new JGL::Texture(loaded_tileset->texture_path);
for (auto layer : loaded_level->layers)
layer->Load();
PopulateQuads();
layer_view->UpdateComponents(loaded_level);
data_ready = true;
}
void EditorApp::LoadMisc()
{
Tileset tileset(std::filesystem::path("tileset.json"));
test_tilesheet = new JGL::Texture(tileset.texture_path);
//auto texture_size = test_tilesheet->GetDimensions();
tileset_width = tileset.rows;//texture_size.x / grid_pixel_width;
tileset_height = tileset.cols;//texture_size.y / grid_pixel_height;
if (std::filesystem::exists("test.lvl"))
LoadTestFile();
}
void EditorApp::SizeCellIndicatorToTilesetSize()
{
cell_indicator->Size(JUI::UDim2::FromPixels(loaded_tileset->tile_width, loaded_tileset->tile_height));
}
JUI::Window* EditorApp::CreateTilesetViewerWindow(JUI::Widget* parent)
@@ -223,17 +217,17 @@ JUI::Window* EditorApp::CreateTilesetViewerWindow(JUI::Widget* parent)
auto* wind = new Window(parent);
wind->SetTitle("Tileset Viewer");
wind->Size(JUI::UDim2::FromPixels(test_tilesheet->GetDimensions().x, test_tilesheet->GetDimensions().y+20));
wind->Size(JUI::UDim2::FromPixels(loaded_tilesheet->GetDimensions().x, loaded_tilesheet->GetDimensions().y+20));
wind->SetResizable(false);
auto* img = new Image(wind->Content());
img->Content(test_tilesheet);
img->Content(loaded_tilesheet);
img->FitImageToParent(false);
auto* grid_overlay = new JGL::RenderTarget(test_tilesheet->GetDimensions());
auto* grid_overlay = new JGL::RenderTarget(loaded_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);
DrawGrid(AABB2D({0,0}, Vector2(loaded_tilesheet->GetDimensions())), grid_overlay_color);
J2D::End();
auto* grid_overlay_tex = new JGL::Texture(*grid_overlay->GetTexture());
@@ -244,12 +238,13 @@ JUI::Window* EditorApp::CreateTilesetViewerWindow(JUI::Widget* parent)
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);
SizeCellIndicatorToTilesetSize();
return wind;
}
@@ -406,9 +401,15 @@ bool EditorApp::Open()
// 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!!!
// TODO: More sophisticated order-of-operations is required.
// 1. Initialize elements of widgets that are independent of the level itself / the level depends on.
// 2. Initialize the level.
// 3. Initialize widgets that are dependent on the level itself.
LoadMisc();
CreateWidgets();
return true;
}
@@ -424,8 +425,8 @@ Vector2i EditorApp::GetTilesetCellFromMouse()
Vector2 rel_mouse = mouse_v2 - rel;
return Vector2i(
Math::Floor(rel_mouse.x/grid_pixel_width),
Math::Floor(rel_mouse.y/grid_pixel_height));
Math::Floor(rel_mouse.x/GetFocusLayer()->cell_width),
Math::Floor(rel_mouse.y/GetFocusLayer()->cell_height));
}
Vector2i EditorApp::GetGridCellFromMouse()
@@ -438,8 +439,8 @@ Vector2i EditorApp::GetGridCellFromMouse()
mouse_v2 = camera.ScreenToCameraSpace(mouse_v2);
return Vector2i(
Math::Floor(mouse_v2.x/grid_pixel_width),
Math::Floor(mouse_v2.y/grid_pixel_height));
Math::Floor(mouse_v2.x/GetFocusLayer()->cell_width),
Math::Floor(mouse_v2.y/GetFocusLayer()->cell_height));
}
void EditorApp::CameraUpdate(float elapsed)
@@ -471,12 +472,12 @@ int EditorApp::GetTile(const Vector2i& cell)
if (cell.y < 0) return -2;
// out of bounds horizontally
if (cell.x >= grid_rows) return -2;
if (cell.x >= GetFocusLayer()->rows) return -2;
// out of bounds vertically
if (cell.y >= grid_cols) return -2;
if (cell.y >= GetFocusLayer()->cols) return -2;
return grid[cell.x][cell.y];
return GetFocusLayer()->cells[cell.x][cell.y];
}
int EditorApp::GetTile(int x, int y) { return GetTile({x, y}); }
@@ -490,12 +491,12 @@ void EditorApp::SetTile(const Vector2i& cell, int tileID)
if (cell.y < 0) return;
// out of bounds horizontally
if (cell.x >= grid_rows) return;
if (cell.x >= GetFocusLayer()->rows) return;
// out of bounds vertically
if (cell.y >= grid_cols) return;
if (cell.y >= GetFocusLayer()->cols) return;
grid[cell.x][cell.y] = tileID;
GetFocusLayer()->cells[cell.x][cell.y] = tileID;
}
void EditorApp::PlaceTileBrush(const Vector2i& cell)
@@ -506,10 +507,10 @@ void EditorApp::PlaceTileBrush(const Vector2i& cell)
if (cell.y < 0) return;
// out of bounds horizontally
if (cell.x > grid_rows) return;
if (cell.x > GetFocusLayer()->rows) return;
// out of bounds vertically
if (cell.y > grid_cols) return;
if (cell.y > GetFocusLayer()->cols) return;
if (IsKeyDown(Keys::B))
@@ -527,10 +528,10 @@ void EditorApp::EraseTile(const Vector2i& cell)
if (cell.y < 0) return;
// out of bounds horizontally
if (cell.x > grid_rows) return;
if (cell.x > GetFocusLayer()->rows) return;
// out of bounds vertically
if (cell.y > grid_cols) return;
if (cell.y > GetFocusLayer()->cols) return;
SetTile(cell, -1);
}
@@ -548,25 +549,31 @@ void EditorApp::Update(float elapsed)
scene->SetViewportSize(Vector2(vSize));
scene->Update(elapsed);
bool mouse_hovering_ui_element = scene->IsMouseInsideDescendants();
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;
rel_mouse.x *= loaded_tileset->tile_width;
rel_mouse.y *= loaded_tileset->tile_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 (!mouse_hovering_ui_element)
{
if (IsMouseButtonDown(MouseButtons::Left))
PlaceTileBrush(GetGridCellFromMouse());
if (IsMouseButtonDown(MouseButtons::Right))
EraseTile(GetGridCellFromMouse());
if (IsMouseButtonDown(MouseButtons::Right))
EraseTile(GetGridCellFromMouse());
}
}
}
@@ -575,18 +582,20 @@ 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);
int nearest_grid_left = Math::Floor(viewport_topleft.x / loaded_tileset->tile_width);
int nearest_grid_right = Math::Floor(viewport_bottomright.x / loaded_tileset->tile_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);
auto top = Vector2(x * loaded_tileset->tile_width, viewport_topleft.y);
auto bottom = Vector2(x * loaded_tileset->tile_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);
int nearest_grid_top = Math::Floor(viewport_topleft.y / loaded_tileset->tile_height);
int nearest_grid_bottom = Math::Floor(viewport_bottomright.y / loaded_tileset->tile_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);
auto left = Vector2(viewport_topleft.x, y * loaded_tileset->tile_height);
auto right = Vector2(viewport_bottomright.x, y * loaded_tileset->tile_height);
JGL::J2D::DrawDashedLine(color, left, right, 4, 4, 1 / bounds.Height());
}
}
@@ -594,6 +603,11 @@ void EditorApp::DrawGrid(const AABB2D& bounds, const Color4& color)
void EditorApp::DrawGrid(const Color4& color)
{
auto grid_rows = GetFocusLayer()->rows;
auto grid_cols = GetFocusLayer()->cols;
auto grid_pixel_width = GetFocusLayer()->cell_width;
auto grid_pixel_height = GetFocusLayer()->cell_height;
float sw = GetWidth();
float sh = GetHeight();
@@ -604,15 +618,15 @@ void EditorApp::DrawCellPointerOutline()
{
Vector2 rel_mouse = Vector2(GetGridCellFromMouse());
rel_mouse.x *= grid_pixel_width;
rel_mouse.y *= grid_pixel_height;
rel_mouse.x *= GetFocusLayer()->cell_width;
rel_mouse.y *= GetFocusLayer()->cell_height;
J2D::OutlineRect(cell_pointer_outline_color, rel_mouse, Vector2(grid_pixel_width, grid_pixel_height), cell_pointer_outline_width);
J2D::OutlineRect(cell_pointer_outline_color, rel_mouse, Vector2( GetFocusLayer()->cell_width, GetFocusLayer()->cell_height), cell_pointer_outline_width);
}
void EditorApp::DrawTiles()
{
for (int gx = 0; gx < grid_rows; gx++) {
/*for (int gx = 0; gx < grid_rows; gx++) {
for (int gy = 0; gy < grid_cols; gy++) {
auto quad_idx = grid[gx][gy];
@@ -625,12 +639,12 @@ void EditorApp::DrawTiles()
if (quad_idx < 0)
continue;
auto quad = quads[quad_idx];
//auto quad = quads[quad_idx];
J2D::DrawPartialSprite(test_tilesheet, pos,
Vector2(quad.x, quad.y), Vector2(quad.w, quad.h));
//J2D::DrawPartialSprite(test_tilesheet, pos,
// Vector2(quad.x, quad.y), Vector2(quad.w, quad.h));
}
}
}*/
}
void EditorApp::Draw()
@@ -644,10 +658,14 @@ void EditorApp::Draw()
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 (data_ready)
DrawLevel(loaded_level);
else
J2D::DrawString(Colors::White, "Loading Level", 0, 0, 1, 24);
if (grid_overlay_enabled)
DrawGrid(grid_overlay_color);

View File

@@ -1,4 +1,4 @@
#include <EditorCamera.hpp>
#include <App/EditorCamera.hpp>
Matrix4x4 EditorCamera::CurrentTranslationMatrix() const
{

1
src/Data/Entity.cpp Normal file
View File

@@ -0,0 +1 @@
#include <Data/Entity.hpp>

View File

@@ -1,4 +1,4 @@
#include <Layer.hpp>
#include <Data/Layer.hpp>
Layer::Layer(): rows(0), cols(0), cell_width(0), cell_height(0), parallax_x(0), parallax_y(0), cells(nullptr)
{ }
@@ -17,6 +17,11 @@ Layer::Layer(const json::value& json)
Deserialize(json);
}
Layer::~Layer()
{
DeleteGrid();
}
Layer::Layer(const std::filesystem::path& path)
{
auto [json, err] = json::parse(read_file_contents(path));
@@ -50,6 +55,9 @@ void Layer::Deserialize(const json::value& json)
void Layer::InitGrid()
{
if (grid_allocated)
return; // TODO: Throw double allocation.
cells = new int*[rows];
for (int i = 0; i < rows; i++)
@@ -60,14 +68,22 @@ void Layer::InitGrid()
cells[x][y] = -1;
}
}
grid_allocated = true;
}
void Layer::DeleteGrid()
{
if (!grid_allocated)
return;
for (int i = 0; i < rows; i++)
delete[] cells[i];
delete[] cells;
grid_allocated = false;
}
void Layer::Resize(int newWidth, int newHeight) {
@@ -115,3 +131,56 @@ void Layer::LoadFromDataBuffer(const int* buffer)
}
}
}
void Layer::WriteToDataBuffer(int* buffer) const
{
//int* buffer = new int[rows*cols];
for (int x = 0; x < rows; x++)
{
for (int y = 0; y < cols; y++)
{
int index = (x*cols + y);
buffer[index] = cells[x][y];
}
}
}
bool Layer::WriteBinaryData(const std::filesystem::path& path) const
{
int* buffer = new int[rows*cols];
WriteToDataBuffer(buffer);
std::ofstream output;
output.open(path, std::ios::out | std::ios::binary);
output.write(reinterpret_cast<const char*>(buffer), rows * cols * sizeof(int));
output.close();
delete[] buffer;
return true;
}
bool Layer::Save()
{ return WriteBinaryData(binary_path); }
bool Layer::Load()
{ return ReadBinaryData(binary_path); }
bool Layer::ReadBinaryData(const std::filesystem::path& path)
{
std::ifstream input;
input.open(path, 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();
InitGrid();
LoadFromDataBuffer(reinterpret_cast<int*>(buffer));
delete[] buffer;
return true;
}

74
src/Data/Level.cpp Normal file
View File

@@ -0,0 +1,74 @@
#include <Data/Level.hpp>
Level::Level(): rows(0), cols(0)
{
}
Level::Level(const std::filesystem::path& path)
{
auto [json, err] = json::parse(read_file_contents(path));
Deserialize(json);
}
Level::Level(const json::value& json)
{
Deserialize(json);
}
void Level::Deserialize(const json::value& json)
{
name = json["name"].as_string();
description = json["description"].as_string();
author = json["author"].as_string();
tileset_path = json["tileset-path"].as_string();
auto layer_json_array = json["layers"].as_array();
for (auto layer_json : layer_json_array)
{
layers.emplace_back(new Layer(layer_json));
}
auto entities_json_array = json["entities"].as_array();
for (auto& entity_json : entities_json_array)
{
entities.emplace_back(new Entity(entity_json));
}
}
json::value Level::Serialize() const
{
json::object data;
data["name"] = name;
data["description"] = description;
data["author"] = author;
data["tileset-path"] = tileset_path;
data["editor-version"] = REDACTED_EDITOR_LIB_VERSION;
data["map-type"] = "OrthogonalTileMap";
data["map-rows"] = (double)rows;
data["map-cols"] = (double)cols;
data["tile-width"] = 16.f;
data["tile-height"] = 16.f;
data["bgcolor"] = JsonConversions::deparse_color_to_hex(Colors::Black);
data["tags"] = JsonConversions::deparse_string_list(tags);
json::array layers_json;
for (auto& layer : this->layers)
{
layers_json.push_back(layer->Serialize());
}
data["layers"] = layers_json;
json::array entities_json;
for (auto& entity : this->entities)
{
entities_json.push_back(entity->Serialize());
}
data["entities"] = entities_json;
return data;
}

View File

@@ -1,4 +1,4 @@
#include <Tileset.hpp>
#include <Data/Tileset.hpp>
Tileset::Tileset():
tile_width(0),tile_height(0), tile_spacing(0),