feat: add vec3 type to nhollman json / fix: scene serialisation stuff
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
#include <destrum/Scene/SceneManager.h>
|
||||
#include <destrum/Serialization/ComponentFactory.h>
|
||||
#include <destrum/Serialization/ComponentRegistry.h>
|
||||
#include <destrum/Serialization/GlmJson.h>
|
||||
#include <destrum/Serialization/SceneSerializer.h>
|
||||
#include <destrum/Physics/SimplePhysicsWorld.h>
|
||||
#include <destrum/Physics/JoltPhysicsWorld.h>
|
||||
@@ -1341,6 +1342,137 @@ namespace {
|
||||
|
||||
SceneManager::GetInstance().Destroy();
|
||||
}
|
||||
std::string readFile(const std::filesystem::path& path)
|
||||
{
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
return {std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
|
||||
}
|
||||
|
||||
void testSceneSaveRejectsNonFinite()
|
||||
{
|
||||
Scene& scene = SceneManager::GetInstance().CreateScene("non-finite");
|
||||
GameObject* object = scene.CreateGameObject("object");
|
||||
scene.CommitPendingAdditions();
|
||||
|
||||
const auto path = std::filesystem::temp_directory_path() / "destrum_non_finite_scene.json";
|
||||
check(SceneSerializer::Save(scene, path), "a finite scene must save");
|
||||
const std::string goodContents = readFile(path);
|
||||
|
||||
object->GetTransform().SetLocalPosition({std::nanf(""), 0.0f, 0.0f});
|
||||
check(!SceneSerializer::Save(scene, path), "a scene with NaN values must not save");
|
||||
check(readFile(path) == goodContents, "a failed save must leave the previous file untouched");
|
||||
check(!std::filesystem::exists(std::filesystem::path{path} += ".tmp"),
|
||||
"a failed save must not leave its temp file behind");
|
||||
|
||||
std::filesystem::remove(path);
|
||||
SceneManager::GetInstance().Destroy();
|
||||
}
|
||||
|
||||
void testSceneValidationErrors()
|
||||
{
|
||||
const auto path = std::filesystem::temp_directory_path() / "destrum_invalid_scene.json";
|
||||
std::ofstream{path}
|
||||
<< R"({"version":2,"name":"bad","objects":[{"id":7,"name":"Lamp","transform":{"position":[0,0,0],"rotation":[0,0,0,1],"scale":[1,1,1]},"parent":0,"components":[{"type":"NotAComponent","enabled":true,"data":{}}]}]})";
|
||||
|
||||
const auto result = SceneSerializer::LoadSceneFile(path);
|
||||
check(!result.success, "a scene with an unknown component must not load");
|
||||
check(result.errorMessage.find("\"Lamp\"") != std::string::npos &&
|
||||
result.errorMessage.find("NotAComponent") != std::string::npos &&
|
||||
result.errorMessage.find("unknown component type") != std::string::npos,
|
||||
"scene validation errors must name the object, component and problem");
|
||||
|
||||
std::ofstream{path}
|
||||
<< R"({"version":2,"objects":[{"id":7,"name":"Lamp","components":[{"type":"Spinner","data":["message","hi"]}]}]})";
|
||||
const auto arrayData = SceneSerializer::LoadSceneFile(path);
|
||||
check(!arrayData.success && arrayData.errorMessage.find("must be an object") != std::string::npos,
|
||||
"component data that is not an object must be rejected with a reason");
|
||||
|
||||
std::filesystem::remove(path);
|
||||
}
|
||||
|
||||
void testSceneLoadRestoresBindings()
|
||||
{
|
||||
Scene& scene = SceneManager::GetInstance().CreateScene("bindings");
|
||||
int registered = 0;
|
||||
int unregistered = 0;
|
||||
scene.SetRegisterBindings([®istered] { ++registered; });
|
||||
scene.SetUnregisterBindings([&unregistered] { ++unregistered; });
|
||||
scene.Load();
|
||||
const int registeredBefore = registered;
|
||||
const int unregisteredBefore = unregistered;
|
||||
scene.CreateGameObject("object");
|
||||
scene.CommitPendingAdditions();
|
||||
|
||||
const auto path = std::filesystem::temp_directory_path() / "destrum_bindings_scene.json";
|
||||
check(SceneSerializer::Save(scene, path), "the bindings scene must save");
|
||||
check(SceneSerializer::Load(scene, path), "the bindings scene must load");
|
||||
std::filesystem::remove(path);
|
||||
|
||||
check(registered - registeredBefore == 1 && unregistered - unregisteredBefore == 1, "loading a scene in place must register its bindings again");
|
||||
|
||||
scene.SetRegisterBindings(nullptr);
|
||||
scene.SetUnregisterBindings(nullptr);
|
||||
SceneManager::GetInstance().Destroy();
|
||||
}
|
||||
|
||||
void testSceneLoadDropsMissingCamera()
|
||||
{
|
||||
Scene& scene = SceneManager::GetInstance().CreateScene("missing camera");
|
||||
const auto path = std::filesystem::temp_directory_path() / "destrum_missing_camera_scene.json";
|
||||
std::ofstream{path}
|
||||
<< R"({"version":2,"currentCamera":4242,"objects":[{"id":11,"name":"object"}]})";
|
||||
|
||||
check(SceneSerializer::Load(scene, path), "a scene with a dangling camera id must still load");
|
||||
check(scene.GetCurrentCamera() == nullptr, "a dangling current camera id must be ignored");
|
||||
|
||||
std::filesystem::remove(path);
|
||||
SceneManager::GetInstance().Destroy();
|
||||
}
|
||||
|
||||
void testGlmJson()
|
||||
{
|
||||
const nlohmann::json vec = glm::vec3{1.0f, 2.5f, -3.0f};
|
||||
check(vec == nlohmann::json::array({1.0f, 2.5f, -3.0f}), "vec3 must serialize as a 3 number array");
|
||||
check(vec.get<glm::vec3>() == glm::vec3{1.0f, 2.5f, -3.0f}, "vec3 must round-trip through json");
|
||||
|
||||
const glm::quat rotation = glm::angleAxis(0.5f, glm::vec3{0.0f, 1.0f, 0.0f});
|
||||
const nlohmann::json quat = rotation;
|
||||
check(quat.size() == 4 && close(quat.at(3).get<float>(), rotation.w),
|
||||
"quaternions must serialize as [x, y, z, w]");
|
||||
check(quat.get<glm::quat>() == rotation, "quaternions must round-trip through json");
|
||||
|
||||
bool rejected = false;
|
||||
try {
|
||||
(void)nlohmann::json::array({1.0f, 2.0f}).get<glm::vec3>();
|
||||
} catch (const std::exception&) {
|
||||
rejected = true;
|
||||
}
|
||||
check(rejected, "a vec3 must reject arrays of the wrong size");
|
||||
|
||||
const nlohmann::json data = {{"axis", nlohmann::json::array({0, 1, 0})}};
|
||||
check(data.value("axis", glm::vec3{0.0f}) == glm::vec3{0.0f, 1.0f, 0.0f} &&
|
||||
data.value("missing", glm::vec3{7.0f}) == glm::vec3{7.0f},
|
||||
"json value() must work with glm defaults");
|
||||
}
|
||||
|
||||
void testPrimitiveMeshKeys()
|
||||
{
|
||||
const glm::vec3 size{0.35f, 0.12f, 3.0f};
|
||||
const std::string key = PrimitiveMeshes::BoxKey(size);
|
||||
check(PrimitiveMeshes::IsPrimitiveKey(key), "box keys must be primitive keys");
|
||||
check(PrimitiveMeshes::MakeBox(size).name == key, "boxes must be named by their key");
|
||||
|
||||
const auto rebuilt = PrimitiveMeshes::MakeFromKey(key + "#3");
|
||||
check(rebuilt.has_value() && rebuilt->maxPos == size * 0.5f,
|
||||
"a box must rebuild exactly from its key, ignoring the duplicate suffix");
|
||||
check(PrimitiveMeshes::StripDuplicateSuffix("BulbMaterial#12") == "BulbMaterial" &&
|
||||
PrimitiveMeshes::StripDuplicateSuffix("game://a.glb#mesh") == "game://a.glb#mesh",
|
||||
"only numeric duplicate suffixes must be stripped");
|
||||
check(!PrimitiveMeshes::MakeFromKey("prim://box?size=1,2").has_value() &&
|
||||
!PrimitiveMeshes::MakeFromKey("prim://box?size=1,-2,3").has_value() &&
|
||||
!PrimitiveMeshes::MakeFromKey("Box").has_value(),
|
||||
"invalid primitive keys must not build a mesh");
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
@@ -1380,6 +1512,12 @@ int main()
|
||||
testKinematicBodiesMoveWithVelocity();
|
||||
testRenderer2DShapes();
|
||||
testFontAtlasAndLayout();
|
||||
testSceneSaveRejectsNonFinite();
|
||||
testSceneValidationErrors();
|
||||
testSceneLoadRestoresBindings();
|
||||
testSceneLoadDropsMissingCamera();
|
||||
testGlmJson();
|
||||
testPrimitiveMeshKeys();
|
||||
std::cout << "destrum tests passed\n";
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user