diff --git a/modules/tmj-format/inc/tactile/tmj-format/emit/tmj_object_emitter.hpp b/modules/tmj-format/inc/tactile/tmj-format/emit/tmj_object_emitter.hpp new file mode 100644 index 0000000000..f27d9e0d03 --- /dev/null +++ b/modules/tmj-format/inc/tactile/tmj-format/emit/tmj_object_emitter.hpp @@ -0,0 +1,33 @@ +// Copyright (C) 2023 Albin Johansson (GNU General Public License v3.0) + +#pragma once + +#include "tactile/core/container/vector.hpp" +#include "tactile/core/io/ir.hpp" +#include "tactile/core/prelude.hpp" +#include "tactile/tmj-format/api.hpp" +#include "tactile/tmj-format/common/json.hpp" + +namespace tactile::tmj { + +/** + * \brief Converts a map object to a TMJ JSON object. + * + * \param object the object to convert. + * + * \return a JSON object. + */ +[[nodiscard]] +TACTILE_TMJ_API auto emit_object(const ir::Object& object) -> JSON; + +/** + * \brief Converts a group of map objects to an array of TMJ JSON objects. + * + * \param objects the objects to convert. + * + * \return a JSON array. + */ +[[nodiscard]] +TACTILE_TMJ_API auto emit_object_array(const Vector& objects) -> JSON; + +} // namespace tactile::tmj diff --git a/modules/tmj-format/src/tactile/tmj-format/emit/tmj_object_emitter.cpp b/modules/tmj-format/src/tactile/tmj-format/emit/tmj_object_emitter.cpp new file mode 100644 index 0000000000..b69906d36a --- /dev/null +++ b/modules/tmj-format/src/tactile/tmj-format/emit/tmj_object_emitter.cpp @@ -0,0 +1,46 @@ +// Copyright (C) 2023 Albin Johansson (GNU General Public License v3.0) + +#include "tactile/tmj-format/emit/tmj_object_emitter.hpp" + +#include "tactile/tmj-format/emit/tmj_meta_emitter.hpp" + +namespace tactile::tmj { + +auto emit_object(const ir::Object& object) -> JSON +{ + auto object_json = JSON::object(); + + object_json["id"] = object.id; + object_json["name"] = object.meta.name; + object_json["x"] = object.x; + object_json["y"] = object.y; + object_json["width"] = object.width; + object_json["height"] = object.height; + object_json["visible"] = object.visible; + object_json["type"] = object.tag; + object_json["rotation"] = 0; + object_json["properties"] = emit_property_array(object.meta); + + // TMJ objects are assumed to be rectangles if there's no point or ellipse indicators. + if (object.type == ObjectType::kPoint) { + object_json["point"] = true; + } + else if (object.type == ObjectType::kEllipse) { + object_json["ellipse"] = true; + } + + return object_json; +} + +auto emit_object_array(const Vector& objects) -> JSON +{ + auto object_json_array = JSON::array(); + + for (const auto& object : objects) { + object_json_array += emit_object(object); + } + + return object_json_array; +} + +} // namespace tactile::tmj diff --git a/modules/tmj-format/test/emit/tmj_object_emitter_test.cpp b/modules/tmj-format/test/emit/tmj_object_emitter_test.cpp new file mode 100644 index 0000000000..72e0454274 --- /dev/null +++ b/modules/tmj-format/test/emit/tmj_object_emitter_test.cpp @@ -0,0 +1,79 @@ +// Copyright (C) 2023 Albin Johansson (GNU General Public License v3.0) + +#include "tactile/tmj-format/emit/tmj_object_emitter.hpp" + +#include + +using namespace tactile; + +class ParameterizedTmjObjectEmitterTest : public testing::TestWithParam {}; + +INSTANTIATE_TEST_SUITE_P(AllObjectTypes, + ParameterizedTmjObjectEmitterTest, + testing::Values(ObjectType::kRect, + ObjectType::kEllipse, + ObjectType::kPoint)); + +/// \tests tactile::tmj::emit_object +TEST_P(ParameterizedTmjObjectEmitterTest, EmitObject) +{ + const ObjectType object_type = GetParam(); + + const ir::Object object { + .meta = + { + .name = "ObjectName", + .properties = + { + ir::NamedAttribute {.name = "P1", .value = 42}, + ir::NamedAttribute {.name = "P2", .value = "XYZ"}, + }, + .components = {}, + }, + .id = 72, + .type = object_type, + .x = 83, + .y = -21, + .width = 129, + .height = 310, + .tag = "Object::tag", + .visible = true, + }; + + const auto object_json = tmj::emit_object(object); + ASSERT_TRUE(object_json.is_object()); + + EXPECT_EQ(object_json.at("name"), object.meta.name); + EXPECT_EQ(object_json.at("id"), object.id); + EXPECT_EQ(object_json.at("x"), object.x); + EXPECT_EQ(object_json.at("y"), object.y); + EXPECT_EQ(object_json.at("width"), object.width); + EXPECT_EQ(object_json.at("height"), object.height); + EXPECT_EQ(object_json.at("type"), object.tag); + EXPECT_EQ(object_json.at("visible"), object.visible); + + if (object_type == ObjectType::kPoint) { + EXPECT_TRUE(object_json.contains("point")); + EXPECT_FALSE(object_json.contains("ellipse")); + } + else if (object_type == ObjectType::kEllipse) { + EXPECT_FALSE(object_json.contains("point")); + EXPECT_TRUE(object_json.contains("ellipse")); + } + else { + EXPECT_FALSE(object_json.contains("point")); + EXPECT_FALSE(object_json.contains("ellipse")); + } + + ASSERT_TRUE(object_json.contains("properties")); + ASSERT_EQ(object_json.at("properties").size(), object.meta.properties.size()); + + EXPECT_EQ(object_json.at("properties").at(0).at("name"), "P1"); + EXPECT_EQ(object_json.at("properties").at(0).at("type"), "int"); + EXPECT_EQ(object_json.at("properties").at(0).at("value"), 42); + + EXPECT_EQ(object_json.at("properties").at(1).at("name"), "P2"); + EXPECT_EQ(object_json.at("properties").at(1).at("type"), "string"); + EXPECT_EQ(object_json.at("properties").at(1).at("value"), "XYZ"); +} +