466 lines
23 KiB
C++
466 lines
23 KiB
C++
#include "procgen/editing/MeshModifierGraph.h"
|
|
#include "procgen/editing/SplinePathDocument.h"
|
|
#include "procgen/editing/SplinePathGizmo.h"
|
|
|
|
#include "editing/EditingGraph.h"
|
|
#include "zeroerr/unittest.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
using namespace eve::editing;
|
|
using namespace eve::procgen_editing;
|
|
|
|
namespace {
|
|
|
|
SplinePathControlPoint point(const char* id, std::int64_t order, double x, double y, double z = 0.0) {
|
|
SplinePathControlPoint result;
|
|
result.id = StableId(id);
|
|
result.order = order;
|
|
result.x = x;
|
|
result.y = y;
|
|
result.z = z;
|
|
return result;
|
|
}
|
|
|
|
eve::procgen::MeshBuild sourceTriangle() {
|
|
eve::procgen::MeshBuild mesh;
|
|
mesh.addVertex(0.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f);
|
|
mesh.addVertex(1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 1.f, 0.f);
|
|
mesh.addVertex(0.f, 1.f, 0.f, 0.f, 0.f, 1.f, 0.f, 1.f);
|
|
mesh.addTriangle(0, 1, 2);
|
|
return mesh;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("editor.splinePath.operationsAreReversibleAndSnapshotIsAtomic") {
|
|
SplinePathDocument document("road-path");
|
|
auto first = document.makeSetPoint(point("a", 0, 0.0, 0.0));
|
|
auto second = document.makeSetPoint(point("b", 1, 2.0, 3.0));
|
|
REQUIRE(first.ok());
|
|
REQUIRE(second.ok());
|
|
REQUIRE(document.applyDomainOperation(first.value()).ok());
|
|
REQUIRE(document.applyDomainOperation(second.value()).ok());
|
|
|
|
auto moved = document.makeSetPoint(point("b", 1, 4.0, 3.0));
|
|
REQUIRE(moved.ok());
|
|
REQUIRE(document.applyDomainOperation(moved.value()).ok());
|
|
DomainOperation undo = moved.value();
|
|
undo.type = moved.value().inverseType;
|
|
undo.payload = moved.value().inverse;
|
|
REQUIRE(document.applyDomainOperation(undo).ok());
|
|
CHECK(std::abs(document.points()[1].x - 2.0) < 1e-9);
|
|
|
|
auto settings = document.makeSetSettings({"linear", true});
|
|
REQUIRE(settings.ok());
|
|
REQUIRE(document.applyDomainOperation(settings.value()).ok());
|
|
auto snapshot = document.snapshotValue();
|
|
auto* object = snapshot.getIf<EditorValue::Object>();
|
|
REQUIRE(object != nullptr);
|
|
(*object)["futureField"] = EditorValue("preserved-by-newer-writer");
|
|
|
|
SplinePathDocument restored("road-path-copy");
|
|
REQUIRE(restored.loadSnapshot(snapshot).ok());
|
|
CHECK(restored.settings().kind == "linear");
|
|
CHECK(restored.settings().closed);
|
|
CHECK_EQ(restored.points().size(), std::size_t(2));
|
|
REQUIRE(restored.compilePath().ok());
|
|
|
|
const auto before = restored.snapshotValue();
|
|
(*object)["schemaVersion"] = EditorValue(std::int64_t{99});
|
|
CHECK(!restored.loadSnapshot(snapshot).ok());
|
|
CHECK(restored.snapshotValue() == before);
|
|
}
|
|
|
|
TEST_CASE("editor.splinePath.pointProfileRoundTripsAndOldSnapshotsDefault") {
|
|
SplinePathDocument document("profile-path");
|
|
auto profiled = point("a", 0, 0.0, 0.0);
|
|
profiled.rollDegrees = 35.0;
|
|
profiled.scaleX = 1.75;
|
|
profiled.scaleY = 0.6;
|
|
REQUIRE(document.applyDomainOperation(document.makeSetPoint(profiled).value()).ok());
|
|
REQUIRE(document.applyDomainOperation(document.makeSetPoint(point("b", 1, 0.0, 2.0)).value()).ok());
|
|
SplinePathDocument restored("profile-restored");
|
|
REQUIRE(restored.loadSnapshot(document.snapshotValue()).ok());
|
|
CHECK(std::abs(restored.points().front().rollDegrees - 35.0) < 1e-9);
|
|
CHECK(std::abs(restored.points().front().scaleX - 1.75) < 1e-9);
|
|
|
|
auto legacy = document.snapshotValue();
|
|
auto* object = legacy.getIf<EditorValue::Object>();
|
|
REQUIRE(object != nullptr);
|
|
auto* points = (*object)["points"].getIf<EditorValue::Array>();
|
|
REQUIRE(points != nullptr);
|
|
for (auto& value : *points) {
|
|
auto* fields = value.getIf<EditorValue::Object>();
|
|
REQUIRE(fields != nullptr);
|
|
fields->erase("rollDegrees");
|
|
fields->erase("scaleX");
|
|
fields->erase("scaleY");
|
|
}
|
|
REQUIRE(restored.loadSnapshot(legacy).ok());
|
|
CHECK(std::abs(restored.points().front().rollDegrees) < 1e-9);
|
|
CHECK(std::abs(restored.points().front().scaleX - 1.0) < 1e-9);
|
|
CHECK(std::abs(restored.points().front().scaleY - 1.0) < 1e-9);
|
|
CHECK(std::abs(restored.points().front().pitchDegrees) < 1e-9);
|
|
CHECK(std::abs(restored.points().front().yawDegrees) < 1e-9);
|
|
}
|
|
|
|
TEST_CASE("editor.splinePath.bezierInsertionPreservesCurveAndIsOneReversibleOperation") {
|
|
SplinePathDocument document("bezier-insert");
|
|
REQUIRE(document.applyDomainOperation(document.makeSetSettings({"bezier", false}).value()).ok());
|
|
auto a = point("a", 0, 0.0, 0.0);
|
|
a.outX = 1.0;
|
|
a.outY = 2.0;
|
|
auto b = point("b", 1, 4.0, 0.0);
|
|
b.inX = -1.0;
|
|
b.inY = 2.0;
|
|
REQUIRE(document.applyDomainOperation(document.makeSetPoint(a).value()).ok());
|
|
REQUIRE(document.applyDomainOperation(document.makeSetPoint(b).value()).ok());
|
|
const auto beforePath = document.compilePath();
|
|
REQUIRE(beforePath.ok());
|
|
const auto beforeQuarter = beforePath.value().evaluateResult(0.25f);
|
|
const auto beforeMiddle = beforePath.value().evaluateResult(0.5f);
|
|
REQUIRE(beforeQuarter.ok());
|
|
REQUIRE(beforeMiddle.ok());
|
|
|
|
auto split = document.makeInsertPointAt(0, 0.5, StableId("middle"));
|
|
REQUIRE(split.ok());
|
|
REQUIRE(document.applyDomainOperation(split.value()).ok());
|
|
CHECK_EQ(document.points().size(), 3u);
|
|
const auto afterPath = document.compilePath();
|
|
REQUIRE(afterPath.ok());
|
|
const auto afterQuarter = afterPath.value().evaluateResult(0.25f);
|
|
const auto afterMiddle = afterPath.value().evaluateResult(0.5f);
|
|
REQUIRE(afterQuarter.ok());
|
|
REQUIRE(afterMiddle.ok());
|
|
CHECK(std::abs(afterQuarter.value().x - beforeQuarter.value().x) < 1e-5f);
|
|
CHECK(std::abs(afterQuarter.value().y - beforeQuarter.value().y) < 1e-5f);
|
|
CHECK(std::abs(afterMiddle.value().x - beforeMiddle.value().x) < 1e-5f);
|
|
CHECK(std::abs(afterMiddle.value().y - beforeMiddle.value().y) < 1e-5f);
|
|
|
|
DomainOperation undo = split.value();
|
|
undo.type = split.value().inverseType;
|
|
undo.payload = split.value().inverse;
|
|
REQUIRE(document.applyDomainOperation(undo).ok());
|
|
CHECK_EQ(document.points().size(), 2u);
|
|
}
|
|
|
|
TEST_CASE("editor.splinePath.snapAndFlipProduceReversibleAuthoringOperations") {
|
|
SplinePathDocument document("snap-flip");
|
|
auto a = point("a", 0, 0.24, 0.76, -0.26);
|
|
a.outX = 0.7;
|
|
auto b = point("b", 1, 2.0, 1.0);
|
|
b.inX = -0.4;
|
|
REQUIRE(document.applyDomainOperation(document.makeSetPoint(a).value()).ok());
|
|
REQUIRE(document.applyDomainOperation(document.makeSetPoint(b).value()).ok());
|
|
auto snap = document.makeSnapPoint(StableId("a"), 0.5);
|
|
REQUIRE(snap.ok());
|
|
REQUIRE(document.applyDomainOperation(snap.value()).ok());
|
|
CHECK(std::abs(document.points()[0].x) < 1e-9);
|
|
CHECK(std::abs(document.points()[0].y - 1.0) < 1e-9);
|
|
CHECK(std::abs(document.points()[0].z + 0.5) < 1e-9);
|
|
|
|
auto flip = document.makeFlipDirection();
|
|
REQUIRE(flip.ok());
|
|
REQUIRE(document.applyDomainOperation(flip.value()).ok());
|
|
CHECK_EQ(document.points()[0].id, StableId("b"));
|
|
CHECK(std::abs(document.points()[0].outX + 0.4) < 1e-9);
|
|
CHECK_EQ(document.points()[1].id, StableId("a"));
|
|
CHECK(std::abs(document.points()[1].inX - 0.7) < 1e-9);
|
|
}
|
|
|
|
TEST_CASE("editor.splinePath.splitAndConnectChunksRoundTripWithoutGizmoBridge") {
|
|
SplinePathDocument document("chunks");
|
|
REQUIRE(document.applyDomainOperation(document.makeSetSettings({"linear", false}).value()).ok());
|
|
REQUIRE(document.applyDomainOperation(document.makeSetPoint(point("a", 0, 0.0, 0.0)).value()).ok());
|
|
REQUIRE(document.applyDomainOperation(document.makeSetPoint(point("b", 1, 1.0, 0.0)).value()).ok());
|
|
REQUIRE(document.applyDomainOperation(document.makeSetPoint(point("c", 2, 10.0, 0.0)).value()).ok());
|
|
REQUIRE(document.applyDomainOperation(document.makeSetPoint(point("d", 3, 11.0, 0.0)).value()).ok());
|
|
auto split = document.makeSetChunkBreak(StableId("c"), true);
|
|
REQUIRE(split.ok());
|
|
REQUIRE(document.applyDomainOperation(split.value()).ok());
|
|
auto path = document.compilePath();
|
|
REQUIRE(path.ok());
|
|
CHECK_EQ(path.value().chunkCount(), 2);
|
|
auto gizmo = SplinePathGizmoBuilder().build(document, 12);
|
|
CHECK(gizmo.status == EditorStatus::Applied);
|
|
CHECK_EQ(std::count_if(gizmo.primitives.begin(), gizmo.primitives.end(),
|
|
[](const auto& primitive) { return primitive.id.find("spline.chunk.0.segment") == 0; }),
|
|
5);
|
|
CHECK_EQ(std::count_if(gizmo.primitives.begin(), gizmo.primitives.end(),
|
|
[](const auto& primitive) { return primitive.id.find("spline.chunk.1.segment") == 0; }),
|
|
5);
|
|
auto connect = document.makeSetChunkBreak(StableId("c"), false);
|
|
REQUIRE(connect.ok());
|
|
REQUIRE(document.applyDomainOperation(connect.value()).ok());
|
|
CHECK_EQ(document.compilePath().value().chunkCount(), 1);
|
|
}
|
|
|
|
TEST_CASE("editor.splinePath.rotationResetCenterAndAxisMirrorAreReversible") {
|
|
SplinePathDocument document("point-tools");
|
|
REQUIRE(document.applyDomainOperation(document.makeSetPoint(point("a", 0, 0.0, 0.0)).value()).ok());
|
|
REQUIRE(document.applyDomainOperation(document.makeSetPoint(point("b", 1, 1.2, 3.0)).value()).ok());
|
|
REQUIRE(document.applyDomainOperation(document.makeSetPoint(point("c", 2, 4.0, 2.0)).value()).ok());
|
|
auto rotation = document.makeSetPointRotation(StableId("b"), 20.0, -35.0, 15.0);
|
|
REQUIRE(rotation.ok());
|
|
REQUIRE(document.applyDomainOperation(rotation.value()).ok());
|
|
CHECK(std::abs(document.points()[1].pitchDegrees - 20.0) < 1e-9);
|
|
CHECK(std::abs(document.points()[1].yawDegrees + 35.0) < 1e-9);
|
|
auto reset = document.makeResetPointRotation(StableId("b"));
|
|
REQUIRE(reset.ok());
|
|
REQUIRE(document.applyDomainOperation(reset.value()).ok());
|
|
CHECK(std::abs(document.points()[1].rollDegrees) < 1e-9);
|
|
auto center = document.makeCenterPoint(StableId("b"));
|
|
REQUIRE(center.ok());
|
|
REQUIRE(document.applyDomainOperation(center.value()).ok());
|
|
CHECK(std::abs(document.points()[1].x - 2.0) < 1e-9);
|
|
CHECK(std::abs(document.points()[1].y - 1.0) < 1e-9);
|
|
auto mirror = document.makeMirrorAxis("x");
|
|
REQUIRE(mirror.ok());
|
|
REQUIRE(document.applyDomainOperation(mirror.value()).ok());
|
|
CHECK(std::abs(document.points()[1].x + 2.0) < 1e-9);
|
|
DomainOperation undo = mirror.value();
|
|
undo.type = mirror.value().inverseType;
|
|
undo.payload = mirror.value().inverse;
|
|
REQUIRE(document.applyDomainOperation(undo).ok());
|
|
CHECK(std::abs(document.points()[1].x - 2.0) < 1e-9);
|
|
}
|
|
|
|
TEST_CASE("editor.splinePath.snapAllAppendAndDeleteChunkAreAtomicAndReversible") {
|
|
SplinePathDocument document("chunk-tools");
|
|
REQUIRE(document.applyDomainOperation(document.makeSetSettings({"linear", false}).value()).ok());
|
|
REQUIRE(document.applyDomainOperation(document.makeSetPoint(point("a", 0, 0.24, 0.76)).value()).ok());
|
|
REQUIRE(document.applyDomainOperation(document.makeSetPoint(point("b", 1, 1.26, 1.74)).value()).ok());
|
|
auto snapAll = document.makeSnapAll(0.5);
|
|
REQUIRE(snapAll.ok());
|
|
REQUIRE(document.applyDomainOperation(snapAll.value()).ok());
|
|
CHECK(std::abs(document.points()[0].x) < 1e-9);
|
|
CHECK(std::abs(document.points()[0].y - 1.0) < 1e-9);
|
|
CHECK(std::abs(document.points()[1].x - 1.5) < 1e-9);
|
|
CHECK(std::abs(document.points()[1].y - 1.5) < 1e-9);
|
|
|
|
auto append = document.makeAppendChunk(point("c", 90, 10.0, 0.0), point("d", 91, 12.0, 0.0));
|
|
REQUIRE(append.ok());
|
|
REQUIRE(document.applyDomainOperation(append.value()).ok());
|
|
REQUIRE(document.compilePath().ok());
|
|
CHECK_EQ(document.compilePath().value().chunkCount(), 2);
|
|
CHECK(document.points()[2].breakBefore);
|
|
|
|
auto remove = document.makeDeleteChunk(0);
|
|
REQUIRE(remove.ok());
|
|
REQUIRE(document.applyDomainOperation(remove.value()).ok());
|
|
REQUIRE_EQ(document.points().size(), 2u);
|
|
CHECK_EQ(document.points()[0].id, StableId("c"));
|
|
CHECK(!document.points()[0].breakBefore);
|
|
DomainOperation undo = remove.value();
|
|
undo.type = remove.value().inverseType;
|
|
undo.payload = remove.value().inverse;
|
|
REQUIRE(document.applyDomainOperation(undo).ok());
|
|
CHECK_EQ(document.compilePath().value().chunkCount(), 2);
|
|
}
|
|
|
|
TEST_CASE("editor.splinePath.snapshotFeedsModifierGraphPreview") {
|
|
SplinePathDocument path("graph-path");
|
|
REQUIRE(path.applyDomainOperation(path.makeSetSettings({"linear", false}).value()).ok());
|
|
REQUIRE(path.applyDomainOperation(path.makeSetPoint(point("a", 0, 0.0, 0.0)).value()).ok());
|
|
REQUIRE(path.applyDomainOperation(path.makeSetPoint(point("b", 1, 2.0, 2.0)).value()).ok());
|
|
|
|
MeshModifierGraphDomain domain;
|
|
auto input = domain.makeNode(GraphNodeId("input"), "mesh.input");
|
|
auto spline = domain.makeNode(GraphNodeId("spline"), "deform.splinePath");
|
|
auto output = domain.makeNode(GraphNodeId("output"), "mesh.output");
|
|
REQUIRE(input.ok());
|
|
REQUIRE(spline.ok());
|
|
REQUIRE(output.ok());
|
|
auto* properties = spline.value().properties.getIf<EditorValue::Object>();
|
|
REQUIRE(properties != nullptr);
|
|
(*properties)["splinePath"] = path.snapshotValue();
|
|
|
|
GraphDocument graph;
|
|
REQUIRE(graph.createNode(input.value()).ok());
|
|
REQUIRE(graph.createNode(spline.value()).ok());
|
|
REQUIRE(graph.createNode(output.value()).ok());
|
|
const auto connect = [&](const char* edge, const char* from, const char* to) {
|
|
const auto* source = graph.findPin(GraphPinId(from));
|
|
const auto* target = graph.findPin(GraphPinId(to));
|
|
REQUIRE(source != nullptr);
|
|
REQUIRE(target != nullptr);
|
|
REQUIRE(graph.connect({StableId(edge), source->id, target->id}, domain.canConnect(*source, *target)).ok());
|
|
};
|
|
connect("e0", "input.out", "spline.in0");
|
|
connect("e1", "spline.out", "output.in0");
|
|
|
|
auto preview = domain.preview(graph.snapshot(domain.domain()), "input", sourceTriangle(), "output");
|
|
CHECK(preview.status == EditorStatus::Applied);
|
|
CHECK(std::abs(preview.mesh.getPositionX(2) - 2.f) < 1e-5f);
|
|
CHECK(std::abs(preview.mesh.getPositionY(2) - 2.f) < 1e-5f);
|
|
}
|
|
|
|
TEST_CASE("editor.splinePath.snapshotGeneratesTubeWithoutSourceMesh") {
|
|
SplinePathDocument path("tube-path");
|
|
REQUIRE(path.applyDomainOperation(path.makeSetSettings({"linear", false}).value()).ok());
|
|
REQUIRE(path.applyDomainOperation(path.makeSetPoint(point("a", 0, 0.0, 0.0)).value()).ok());
|
|
REQUIRE(path.applyDomainOperation(path.makeSetPoint(point("b", 1, 0.0, 3.0)).value()).ok());
|
|
|
|
MeshModifierGraphDomain domain;
|
|
auto tube = domain.makeNode(GraphNodeId("tube"), "mesh.splineTube");
|
|
auto output = domain.makeNode(GraphNodeId("output"), "mesh.output");
|
|
REQUIRE(tube.ok());
|
|
REQUIRE(output.ok());
|
|
auto* properties = tube.value().properties.getIf<EditorValue::Object>();
|
|
REQUIRE(properties != nullptr);
|
|
(*properties)["splinePath"] = path.snapshotValue();
|
|
(*properties)["pathSegments"] = EditorValue(std::int64_t{6});
|
|
(*properties)["radialSegments"] = EditorValue(std::int64_t{8});
|
|
|
|
GraphDocument graph;
|
|
REQUIRE(graph.createNode(tube.value()).ok());
|
|
REQUIRE(graph.createNode(output.value()).ok());
|
|
const auto* source = graph.findPin(GraphPinId("tube.out"));
|
|
const auto* target = graph.findPin(GraphPinId("output.in0"));
|
|
REQUIRE(source != nullptr);
|
|
REQUIRE(target != nullptr);
|
|
REQUIRE(graph.connect({StableId("tube-output"), source->id, target->id}, domain.canConnect(*source, *target)).ok());
|
|
|
|
auto preview = domain.preview(graph.snapshot(domain.domain()), "", eve::procgen::MeshBuild{}, "output");
|
|
CHECK(preview.status == EditorStatus::Applied);
|
|
CHECK_EQ(preview.mesh.getVertexCount(), 83);
|
|
CHECK_EQ(preview.mesh.getGroupCount(), 2);
|
|
}
|
|
|
|
TEST_CASE("editor.splinePath.snapshotGeneratesRibbonWithoutSourceMesh") {
|
|
SplinePathDocument path("ribbon-path");
|
|
REQUIRE(path.applyDomainOperation(path.makeSetSettings({"catmullRom", false}).value()).ok());
|
|
REQUIRE(path.applyDomainOperation(path.makeSetPoint(point("a", 0, -2.0, 0.0)).value()).ok());
|
|
REQUIRE(path.applyDomainOperation(path.makeSetPoint(point("b", 1, 0.0, 1.0)).value()).ok());
|
|
REQUIRE(path.applyDomainOperation(path.makeSetPoint(point("c", 2, 2.0, 0.0)).value()).ok());
|
|
|
|
MeshModifierGraphDomain domain;
|
|
auto ribbon = domain.makeNode(GraphNodeId("ribbon"), "mesh.splineRibbon");
|
|
REQUIRE(ribbon.ok());
|
|
auto* properties = ribbon.value().properties.getIf<EditorValue::Object>();
|
|
REQUIRE(properties != nullptr);
|
|
(*properties)["splinePath"] = path.snapshotValue();
|
|
(*properties)["thickness"] = EditorValue(0.2);
|
|
(*properties)["pathSegments"] = EditorValue(std::int64_t{8});
|
|
GraphDocument graph;
|
|
REQUIRE(graph.createNode(ribbon.value()).ok());
|
|
auto preview = domain.preview(graph.snapshot(domain.domain()), "", eve::procgen::MeshBuild{}, "ribbon");
|
|
CHECK(preview.status == EditorStatus::Applied);
|
|
CHECK_EQ(preview.mesh.getVertexCount(), 80);
|
|
CHECK_EQ(preview.mesh.getGroupCount(), 4);
|
|
}
|
|
|
|
TEST_CASE("editor.splinePath.snapshotGeneratesOwningProfileExtrusion") {
|
|
SplinePathDocument path("profile-path");
|
|
REQUIRE(path.applyDomainOperation(path.makeSetSettings({"linear", false}).value()).ok());
|
|
REQUIRE(path.applyDomainOperation(path.makeSetPoint(point("a", 0, 0.0, 0.0)).value()).ok());
|
|
REQUIRE(path.applyDomainOperation(path.makeSetPoint(point("b", 1, 0.0, 3.0)).value()).ok());
|
|
|
|
MeshModifierGraphDomain domain;
|
|
auto extrusion = domain.makeNode(GraphNodeId("extrusion"), "mesh.splineExtrude");
|
|
REQUIRE(extrusion.ok());
|
|
auto* properties = extrusion.value().properties.getIf<EditorValue::Object>();
|
|
REQUIRE(properties != nullptr);
|
|
(*properties)["splinePath"] = path.snapshotValue();
|
|
(*properties)["pathSegments"] = EditorValue(std::int64_t{3});
|
|
GraphDocument graph;
|
|
REQUIRE(graph.createNode(extrusion.value()).ok());
|
|
auto preview = domain.preview(graph.snapshot(domain.domain()), "", eve::procgen::MeshBuild{}, "extrusion");
|
|
CHECK(preview.status == EditorStatus::Applied);
|
|
CHECK_EQ(preview.mesh.getVertexCount(), 28);
|
|
CHECK_EQ(preview.mesh.getIndexCount(), 84);
|
|
CHECK_EQ(preview.mesh.getMeta("generator", ""), "mesh.splineExtrude");
|
|
}
|
|
|
|
TEST_CASE("editor.splinePath.gizmoDragPreviewsThenCommitsOneOperation") {
|
|
SplinePathDocument document("viewport-path");
|
|
REQUIRE(document.applyDomainOperation(document.makeSetSettings({"bezier", false}).value()).ok());
|
|
auto first = point("a", 0, 0.0, 0.0);
|
|
first.outX = 0.5;
|
|
auto second = point("b", 1, 2.0, 3.0);
|
|
second.inX = -0.5;
|
|
REQUIRE(document.applyDomainOperation(document.makeSetPoint(first).value()).ok());
|
|
REQUIRE(document.applyDomainOperation(document.makeSetPoint(second).value()).ok());
|
|
|
|
const auto gizmo = SplinePathGizmoBuilder().build(document, 16);
|
|
CHECK(gizmo.status == EditorStatus::Applied);
|
|
CHECK_EQ(gizmo.targetRevision, document.revision());
|
|
const auto anchor = std::find_if(gizmo.primitives.begin(), gizmo.primitives.end(),
|
|
[](const auto& primitive) { return primitive.id == "spline.anchor.b"; });
|
|
REQUIRE(anchor != gizmo.primitives.end());
|
|
CHECK(anchor->kind == "sphere");
|
|
|
|
const auto beforeRevision = document.revision();
|
|
SplinePathDragSession drag(&document, 16);
|
|
auto begun = drag.beginDrag(2.0, 3.0, 5.0, 0.0, 0.0, -1.0);
|
|
REQUIRE(begun.ok());
|
|
CHECK(drag.activePoint() == StableId("b"));
|
|
CHECK(drag.activeHandle() == "anchor");
|
|
auto preview = drag.updateDrag(4.0, 3.0, 5.0, 0.0, 0.0, -1.0);
|
|
REQUIRE(preview.ok());
|
|
CHECK(std::abs(preview.value().draft.x - 4.0) < 1e-9);
|
|
CHECK_EQ(document.revision(), beforeRevision);
|
|
CHECK(std::abs(document.points()[1].x - 2.0) < 1e-9);
|
|
|
|
auto operation = drag.finishDrag();
|
|
REQUIRE(operation.ok());
|
|
CHECK_EQ(document.revision(), beforeRevision);
|
|
REQUIRE(document.applyDomainOperation(operation.value()).ok());
|
|
CHECK(std::abs(document.points()[1].x - 4.0) < 1e-9);
|
|
}
|
|
|
|
TEST_CASE("editor.splinePath.gizmoDragRejectsConcurrentDocumentMutation") {
|
|
SplinePathDocument document("conflicted-path");
|
|
REQUIRE(document.applyDomainOperation(document.makeSetPoint(point("a", 0, 0.0, 0.0)).value()).ok());
|
|
REQUIRE(document.applyDomainOperation(document.makeSetPoint(point("b", 1, 0.0, 2.0)).value()).ok());
|
|
SplinePathDragSession drag(&document, 8);
|
|
REQUIRE(drag.beginDrag(0.0, 0.0, 5.0, 0.0, 0.0, -1.0).ok());
|
|
REQUIRE(document.applyDomainOperation(document.makeSetSettings({"linear", false}).value()).ok());
|
|
auto stale = drag.updateDrag(1.0, 0.0, 5.0, 0.0, 0.0, -1.0);
|
|
CHECK(!stale.ok());
|
|
CHECK(stale.code() == EditorStatus::Conflict);
|
|
CHECK(!drag.isDragging());
|
|
}
|
|
|
|
TEST_CASE("editor.splinePath.gizmoKeepsIncompleteAuthoringPointsVisible") {
|
|
SplinePathDocument document("new-path");
|
|
REQUIRE(document.applyDomainOperation(document.makeSetPoint(point("first", 0, 1.0, 2.0)).value()).ok());
|
|
const auto gizmo = SplinePathGizmoBuilder().build(document, 8);
|
|
CHECK(gizmo.status == EditorStatus::Applied);
|
|
CHECK(!gizmo.diagnostics.empty());
|
|
const auto anchor = std::find_if(gizmo.primitives.begin(), gizmo.primitives.end(),
|
|
[](const auto& primitive) { return primitive.id == "spline.anchor.first"; });
|
|
REQUIRE(anchor != gizmo.primitives.end());
|
|
CHECK(std::abs(anchor->position[0] - 1.0) < 1e-9);
|
|
CHECK(std::abs(anchor->position[1] - 2.0) < 1e-9);
|
|
}
|
|
|
|
TEST_CASE("editor.splinePath.gizmoStyleControlsNodeLineAndTextPresentation") {
|
|
SplinePathDocument document("styled-gizmo");
|
|
REQUIRE(document.applyDomainOperation(document.makeSetSettings({"linear", false}).value()).ok());
|
|
REQUIRE(document.applyDomainOperation(document.makeSetPoint(point("a", 0, 0.0, 0.0)).value()).ok());
|
|
REQUIRE(document.applyDomainOperation(document.makeSetPoint(point("b", 1, 2.0, 0.0)).value()).ok());
|
|
SplineGizmoStyle style;
|
|
style.nodeSize = 0.42;
|
|
style.lineColor = {0.8, 0.1, 0.6, 1.0};
|
|
style.textColor = {0.2, 1.0, 0.3, 1.0};
|
|
auto gizmo = SplinePathGizmoBuilder().build(document, 8, 64, style);
|
|
CHECK(gizmo.status == EditorStatus::Applied);
|
|
const auto anchor = std::find_if(gizmo.primitives.begin(), gizmo.primitives.end(),
|
|
[](const auto& primitive) { return primitive.id == "spline.anchor.a"; });
|
|
const auto label = std::find_if(gizmo.primitives.begin(), gizmo.primitives.end(),
|
|
[](const auto& primitive) { return primitive.id == "spline.label.a"; });
|
|
const auto line = std::find_if(gizmo.primitives.begin(), gizmo.primitives.end(), [](const auto& primitive) {
|
|
return primitive.id.find("spline.chunk.0.segment") == 0;
|
|
});
|
|
REQUIRE(anchor != gizmo.primitives.end());
|
|
REQUIRE(label != gizmo.primitives.end());
|
|
REQUIRE(line != gizmo.primitives.end());
|
|
CHECK(std::abs(anchor->radius - 0.42) < 1e-9);
|
|
CHECK_EQ(label->text, "a");
|
|
CHECK(label->color == style.textColor);
|
|
CHECK(line->color == style.lineColor);
|
|
}
|