Skip to content

Commit

Permalink
Add TMJ format object parser
Browse files Browse the repository at this point in the history
  • Loading branch information
albin-johansson committed Jul 20, 2024
1 parent 1f46073 commit e8faa66
Show file tree
Hide file tree
Showing 6 changed files with 354 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ enum class SaveFormatParseError : uint8
kNoPropertyValue,
kBadPropertyType,
kBadPropertyValue,
kNoObjectId,
kNoObjectName,
kNoObjectX,
kNoObjectY,
kNoObjectWidth,
kNoObjectHeight,
kNoObjectVisibility,
};

} // namespace tactile
2 changes: 2 additions & 0 deletions source/tiled_tmj_format/lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ target_sources(tactile-tiled-tmj-format
"src/tactile/tiled_tmj_format/tiled_tmj_format_plugin.cpp"
"src/tactile/tiled_tmj_format/tiled_tmj_format_save_visitor.cpp"
"src/tmj_format_attribute_parser.cpp"
"src/tmj_format_object_parser.cpp"

PUBLIC FILE_SET "HEADERS" BASE_DIRS "inc" FILES
"inc/tactile/tiled_tmj_format/api.hpp"
"inc/tactile/tiled_tmj_format/tiled_tmj_format.hpp"
"inc/tactile/tiled_tmj_format/tiled_tmj_format_plugin.hpp"
"inc/tactile/tiled_tmj_format/tiled_tmj_format_save_visitor.hpp"
"inc/tactile/tiled_tmj_format/tmj_format_attribute_parser.hpp"
"inc/tactile/tiled_tmj_format/tmj_format_object_parser.hpp"
)

tactile_prepare_target(tactile-tiled-tmj-format)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0)

#pragma once

#include <nlohmann/json.hpp>

#include "tactile/base/container/expected.hpp"
#include "tactile/base/io/save/ir.hpp"
#include "tactile/base/io/save/save_format_parse_error.hpp"
#include "tactile/base/prelude.hpp"
#include "tactile/tiled_tmj_format/api.hpp"

namespace tactile {

/**
* Attempts to parse a single Tiled TMJ layer object.
*
* \param object_json The object JSON node.
*
* \return
* The parsed object if successful; an error code otherwise.
*
* \see https://doc.mapeditor.org/en/stable/reference/json-map-format/#object
*/
[[nodiscard]]
TACTILE_TMJ_FORMAT_API auto parse_tiled_tmj_object(const nlohmann::json& object_json)
-> Expected<ir::Object, SaveFormatParseError>;

} // namespace tactile
90 changes: 90 additions & 0 deletions source/tiled_tmj_format/lib/src/tmj_format_object_parser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0)

#include "tactile/tiled_tmj_format/tmj_format_object_parser.hpp"

#include <utility> // move

#include "tactile/tiled_tmj_format/tmj_format_attribute_parser.hpp"

namespace tactile {

auto parse_tiled_tmj_object(const nlohmann::json& object_json)
-> Expected<ir::Object, SaveFormatParseError>
{
if (!object_json.contains("name")) {
return unexpected(SaveFormatParseError::kNoObjectName);
}

ir::Object object {};

if (auto metadata = parse_tiled_tmj_metadata(object_json)) {
object.meta = std::move(*metadata);
}
else {
return propagate_unexpected(metadata);
}

if (const auto id_iter = object_json.find("id"); id_iter != object_json.end()) {
id_iter->get_to(object.id);
}
else {
return unexpected(SaveFormatParseError::kNoObjectId);
}

if (const auto type_iter = object_json.find("type"); type_iter != object_json.end()) {
type_iter->get_to(object.tag);
}

if (const auto x_iter = object_json.find("x"); x_iter != object_json.end()) {
x_iter->get_to(object.position[0]);
}
else {
return unexpected(SaveFormatParseError::kNoObjectX);
}

if (const auto y_iter = object_json.find("y"); y_iter != object_json.end()) {
y_iter->get_to(object.position[1]);
}
else {
return unexpected(SaveFormatParseError::kNoObjectY);
}

if (const auto width_iter = object_json.find("width"); width_iter != object_json.end()) {
width_iter->get_to(object.size[0]);
}
else {
return unexpected(SaveFormatParseError::kNoObjectWidth);
}

if (const auto height_iter = object_json.find("height"); height_iter != object_json.end()) {
height_iter->get_to(object.size[1]);
}
else {
return unexpected(SaveFormatParseError::kNoObjectHeight);
}

if (const auto visible_iter = object_json.find("visible");
visible_iter != object_json.end()) {
visible_iter->get_to(object.visible);
}
else {
return unexpected(SaveFormatParseError::kNoObjectVisibility);
}

if (const auto point_iter = object_json.find("point");
point_iter != object_json.end() && point_iter->is_boolean() && point_iter->get<bool>()) {
object.type = ObjectType::kPoint;
}
else if (const auto ellipse_iter = object_json.find("ellipse");
ellipse_iter != object_json.end() && ellipse_iter->is_boolean() &&
ellipse_iter->get<bool>()) {
object.type = ObjectType::kEllipse;
}
else {
object.type = ObjectType::kRect;
}

return object;
}

} // namespace tactile
1 change: 1 addition & 0 deletions source/tiled_tmj_format/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ target_sources(tactile-tmj-format-test
PRIVATE
"src/main.cpp"
"src/tmj_format_attribute_parser_test.cpp"
"src/tmj_format_object_parser_test.cpp"
)

tactile_prepare_target(tactile-tmj-format-test)
Expand Down
225 changes: 225 additions & 0 deletions source/tiled_tmj_format/test/src/tmj_format_object_parser_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0)

#include "tactile/tiled_tmj_format/tmj_format_object_parser.hpp"

#include <gtest/gtest.h>

namespace tactile::test {

// tactile::parse_tiled_tmj_object
TEST(TmjFormatObjectParser, ParseValidRectangleObject)
{
const nlohmann::json object_json = {
{"name", "R"},
{"id", 1},
{"x", 2.0f},
{"y", 3.0f},
{"width", 4.0f},
{"height", 5.0f},
{"visible", false},
};

const auto object = parse_tiled_tmj_object(object_json);
ASSERT_TRUE(object.has_value());

EXPECT_EQ(object->meta.name, "R");
EXPECT_EQ(object->meta.properties.size(), 0);
EXPECT_EQ(object->meta.components.size(), 0);
EXPECT_EQ(object->id, ObjectID {1});
EXPECT_EQ(object->type, ObjectType::kRect);
EXPECT_EQ(object->position.x(), 2.0f);
EXPECT_EQ(object->position.y(), 3.0f);
EXPECT_EQ(object->size.x(), 4.0f);
EXPECT_EQ(object->size.y(), 5.0f);
EXPECT_EQ(object->tag, "");
EXPECT_FALSE(object->visible);
}

// tactile::parse_tiled_tmj_object
TEST(TmjFormatObjectParser, ParseValidEllipseObject)
{
const nlohmann::json object_json = {
{"name", "E"},
{"id", 10},
{"x", 12.0f},
{"y", 34.0f},
{"width", 56.0f},
{"height", 78.0f},
{"visible", true},
{"ellipse", true},
};

const auto object = parse_tiled_tmj_object(object_json);
ASSERT_TRUE(object.has_value());

EXPECT_EQ(object->meta.name, "E");
EXPECT_EQ(object->meta.properties.size(), 0);
EXPECT_EQ(object->meta.components.size(), 0);
EXPECT_EQ(object->id, ObjectID {10});
EXPECT_EQ(object->type, ObjectType::kEllipse);
EXPECT_EQ(object->position.x(), 12.0f);
EXPECT_EQ(object->position.y(), 34.0f);
EXPECT_EQ(object->size.x(), 56.0f);
EXPECT_EQ(object->size.y(), 78.0f);
EXPECT_EQ(object->tag, "");
EXPECT_TRUE(object->visible);
}

// tactile::parse_tiled_tmj_object
TEST(TmjFormatObjectParser, ParseValidPointObject)
{
const nlohmann::json object_json = {
{"name", "P"},
{"id", 99},
{"x", 50.0f},
{"y", 51.0f},
{"width", 52.0f},
{"height", 53.0f},
{"visible", true},
{"type", "foobar"},
{"point", true},
};

const auto object = parse_tiled_tmj_object(object_json);
ASSERT_TRUE(object.has_value());

EXPECT_EQ(object->meta.name, "P");
EXPECT_EQ(object->meta.properties.size(), 0);
EXPECT_EQ(object->meta.components.size(), 0);
EXPECT_EQ(object->id, ObjectID {99});
EXPECT_EQ(object->type, ObjectType::kPoint);
EXPECT_EQ(object->position.x(), 50.0f);
EXPECT_EQ(object->position.y(), 51.0f);
EXPECT_EQ(object->size.x(), 52.0f);
EXPECT_EQ(object->size.y(), 53.0f);
EXPECT_EQ(object->tag, "foobar");
EXPECT_TRUE(object->visible);
}

// tactile::parse_tiled_tmj_object
TEST(TmjFormatObjectParser, ParseObjectWithoutName)
{
const nlohmann::json object_json = {
{"id", 0},
{"x", 0.0f},
{"y", 0.0f},
{"width", 0.0f},
{"height", 0.0f},
{"visible", false},
};

const auto object = parse_tiled_tmj_object(object_json);
ASSERT_FALSE(object.has_value());

EXPECT_EQ(object.error(), SaveFormatParseError::kNoObjectName);
}

// tactile::parse_tiled_tmj_object
TEST(TmjFormatObjectParser, ParseObjectWithoutId)
{
const nlohmann::json object_json = {
{"name", ""},
{"x", 0.0f},
{"y", 0.0f},
{"width", 0.0f},
{"height", 0.0f},
{"visible", false},
};

const auto object = parse_tiled_tmj_object(object_json);
ASSERT_FALSE(object.has_value());

EXPECT_EQ(object.error(), SaveFormatParseError::kNoObjectId);
}

// tactile::parse_tiled_tmj_object
TEST(TmjFormatObjectParser, ParseObjectWithoutX)
{
const nlohmann::json object_json = {
{"name", ""},
{"id", 0},
{"y", 0.0f},
{"width", 0.0f},
{"height", 0.0f},
{"visible", false},
};

const auto object = parse_tiled_tmj_object(object_json);
ASSERT_FALSE(object.has_value());

EXPECT_EQ(object.error(), SaveFormatParseError::kNoObjectX);
}

// tactile::parse_tiled_tmj_object
TEST(TmjFormatObjectParser, ParseObjectWithoutY)
{
const nlohmann::json object_json = {
{"name", ""},
{"id", 0},
{"x", 0.0f},
{"width", 0.0f},
{"height", 0.0f},
{"visible", false},
};

const auto object = parse_tiled_tmj_object(object_json);
ASSERT_FALSE(object.has_value());

EXPECT_EQ(object.error(), SaveFormatParseError::kNoObjectY);
}

// tactile::parse_tiled_tmj_object
TEST(TmjFormatObjectParser, ParseObjectWithoutWidth)
{
const nlohmann::json object_json = {
{"name", ""},
{"id", 0},
{"x", 0.0f},
{"y", 0.0f},
{"height", 0.0f},
{"visible", false},
};

const auto object = parse_tiled_tmj_object(object_json);
ASSERT_FALSE(object.has_value());

EXPECT_EQ(object.error(), SaveFormatParseError::kNoObjectWidth);
}

// tactile::parse_tiled_tmj_object
TEST(TmjFormatObjectParser, ParseObjectWithoutHeight)
{
const nlohmann::json object_json = {
{"name", ""},
{"id", 0},
{"x", 0.0f},
{"y", 0.0f},
{"width", 0.0f},
{"visible", false},
};

const auto object = parse_tiled_tmj_object(object_json);
ASSERT_FALSE(object.has_value());

EXPECT_EQ(object.error(), SaveFormatParseError::kNoObjectHeight);
}

// tactile::parse_tiled_tmj_object
TEST(TmjFormatObjectParser, ParseObjectWithoutVisibility)
{
const nlohmann::json object_json = {
{"name", ""},
{"id", 0},
{"x", 0.0f},
{"y", 0.0f},
{"width", 0.0f},
{"height", 0.0f},
};

const auto object = parse_tiled_tmj_object(object_json);
ASSERT_FALSE(object.has_value());

EXPECT_EQ(object.error(), SaveFormatParseError::kNoObjectVisibility);
}

} // namespace tactile::test

0 comments on commit e8faa66

Please sign in to comment.