载入中...
搜索中...
未找到
SplinePathGizmo.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <limits>
6
7namespace eve::procgen_editing {
8namespace {
9
10editing::GizmoSnapshot failedSnapshot(const SplinePathDocument& document, const char* rule, std::string message) {
11 editing::GizmoSnapshot result;
12 result.target = document.targetId().value();
13 result.targetRevision = document.revision();
14 result.diagnostics.push_back(editing::ruleDiagnostic(DiagnosticCode::InvalidArgument, RuleId(rule),
15 DiagnosticSeverity::Error, std::move(message)));
16 return result;
17}
18
19std::array<double, 3> position(const SplinePathControlPoint& point) { return {point.x, point.y, point.z}; }
20
21void addLine(editing::GizmoSnapshot& output, std::string id, const std::array<double, 3>& from,
22 const std::array<double, 3>& to, std::array<double, 4> color, bool dashed = false) {
23 std::array<double, 3> direction{to[0] - from[0], to[1] - from[1], to[2] - from[2]};
24 const double length =
25 std::sqrt(direction[0] * direction[0] + direction[1] * direction[1] + direction[2] * direction[2]);
26 if (length <= 1e-12) return;
27 for (double& value : direction) value /= length;
28 output.primitives.push_back({std::move(id), "line", from, {}, direction, color, 0.0, length, dashed});
29}
30
31bool normalizeRay(double x, double y, double z, std::array<double, 3>& output) {
32 const double length = std::sqrt(x * x + y * y + z * z);
33 if (!std::isfinite(length) || length < 1e-9) return false;
34 output = {x / length, y / length, z / length};
35 return true;
36}
37
38} // namespace
39
41 int maximumSamples, SplineGizmoStyle style) const {
42 if (sampleCount < 2 || maximumSamples < 2 || sampleCount > maximumSamples)
43 return failedSnapshot(document, "editor.spline.gizmo-budget",
44 "Spline gizmo sample count exceeds its bounded budget");
45 const auto validColor = [](const auto& color) {
46 return std::all_of(color.begin(), color.end(),
47 [](double value) { return std::isfinite(value) && value >= 0.0 && value <= 1.0; });
48 };
49 if (!std::isfinite(style.nodeSize) || !std::isfinite(style.handleSize) || style.nodeSize <= 0.0 ||
50 style.handleSize <= 0.0 || !validColor(style.lineColor) || !validColor(style.textColor))
51 return failedSnapshot(document, "editor.spline.gizmo-style-invalid",
52 "Spline gizmo sizes must be positive and colors must be finite normalized values");
54 result.target = document.targetId().value();
55 result.targetRevision = document.revision();
56 for (const auto& point : document.points()) {
57 const auto anchor = position(point);
58 result.primitives.push_back(
59 {"spline.anchor." + point.id.value(), "sphere", anchor, {}, {}, {1.0, 0.72, 0.18, 1.0}, style.nodeSize});
60 if (style.showPointLabels) {
62 label.id = "spline.label." + point.id.value();
63 label.kind = "text";
64 label.position = anchor;
65 label.color = style.textColor;
66 label.text = point.id.value();
67 result.primitives.push_back(std::move(label));
68 }
69 if (document.settings().kind != "bezier") continue;
70 const std::array<double, 3> incoming{point.x + point.inX, point.y + point.inY, point.z + point.inZ};
71 const std::array<double, 3> outgoing{point.x + point.outX, point.y + point.outY, point.z + point.outZ};
72 addLine(result, "spline.in-line." + point.id.value(), anchor, incoming, {0.95, 0.4, 0.3, 0.9}, true);
73 addLine(result, "spline.out-line." + point.id.value(), anchor, outgoing, {0.35, 0.9, 0.45, 0.9}, true);
74 result.primitives.push_back(
75 {"spline.in." + point.id.value(), "sphere", incoming, {}, {}, {0.95, 0.4, 0.3, 1.0}, style.handleSize});
76 result.primitives.push_back(
77 {"spline.out." + point.id.value(), "sphere", outgoing, {}, {}, {0.35, 0.9, 0.45, 1.0}, style.handleSize});
78 }
79 auto path = document.compilePath();
80 if (!path.ok()) {
81 if (document.points().size() >= 2)
82 return failedSnapshot(document, "editor.spline.gizmo-incomplete", path.status().describe());
83 result.diagnostics.push_back(editing::ruleDiagnostic(
84 DiagnosticCode::PreconditionViolation, RuleId("editor.spline.gizmo-incomplete"),
85 DiagnosticSeverity::Warning, "Add at least two points to preview the spline curve"));
86 result.status = EditorStatus::Applied;
87 return result;
88 }
89 auto polyline = path.value().polylineResult(sampleCount, false);
90 if (!polyline.ok()) return failedSnapshot(document, "editor.spline.gizmo-evaluate", polyline.status().describe());
91 for (int chunk = 0; chunk < polyline.value().chunkCount(); ++chunk) {
92 auto previous = polyline.value().chunkPointResult(chunk, 0);
93 if (!previous.ok())
94 return failedSnapshot(document, "editor.spline.gizmo-evaluate", previous.status().describe());
95 for (int sample = 1; sample < polyline.value().chunkPointCount(chunk); ++sample) {
96 auto current = polyline.value().chunkPointResult(chunk, sample);
97 if (!current.ok())
98 return failedSnapshot(document, "editor.spline.gizmo-evaluate", current.status().describe());
99 addLine(result, "spline.chunk." + std::to_string(chunk) + ".segment." + std::to_string(sample - 1),
100 {previous.value().x, previous.value().y, previous.value().z},
101 {current.value().x, current.value().y, current.value().z}, style.lineColor);
102 previous = std::move(current);
103 }
104 }
105 result.status = EditorStatus::Applied;
106 return result;
107}
108
110 : document_(document), sampleCount_(sampleCount) {}
111
112Result<SplinePathDragPreview> SplinePathDragSession::previewCurrent() const {
113 if (!document_ || !dragging_)
114 return eve::editing::failed<SplinePathDragPreview>(
115 EditorStatus::Rejected, RuleId("editor.spline.drag-not-active"), "Spline drag has not begun");
116 SplinePathDocument candidate = *document_;
117 auto operation = candidate.makeSetPoint(draft_);
118 if (!operation.ok() || !candidate.applyDomainOperation(operation.value()).ok())
119 return eve::editing::failed<SplinePathDragPreview>(EditorStatus::Rejected,
120 RuleId("editor.spline.drag-preview-invalid"),
121 "Spline draft could not be previewed");
123 result.status = EditorStatus::Applied;
124 result.point = activePoint_;
125 result.handle = activeHandle_;
126 result.draft = draft_;
127 result.gizmo = SplinePathGizmoBuilder().build(candidate, sampleCount_);
128 if (result.gizmo.status != EditorStatus::Applied) result.status = result.gizmo.status;
129 return eve::editing::applied<SplinePathDragPreview>(std::move(result));
130}
131
133 double rayOriginZ, double rayDirectionX,
134 double rayDirectionY, double rayDirectionZ) {
135 cancelDrag();
136 if (!document_)
137 return eve::editing::failed<SplinePathDragPreview>(
138 EditorStatus::Rejected, RuleId("editor.spline.drag-target-required"), "Spline drag target is unavailable");
139 std::array<double, 3> direction;
140 if (!normalizeRay(rayDirectionX, rayDirectionY, rayDirectionZ, direction))
141 return eve::editing::failed<SplinePathDragPreview>(EditorStatus::Rejected,
142 RuleId("editor.spline.drag-ray-invalid"),
143 "Spline drag requires a finite non-zero pointer ray");
144 const std::array<double, 3> origin{rayOriginX, rayOriginY, rayOriginZ};
145 const auto gizmo = SplinePathGizmoBuilder().build(*document_, sampleCount_);
146 if (gizmo.status != EditorStatus::Applied)
147 return eve::editing::failed<SplinePathDragPreview>(EditorStatus::Rejected,
148 RuleId("editor.spline.drag-gizmo-invalid"),
149 "Spline gizmo is not available for picking");
150 double closest = std::numeric_limits<double>::infinity();
151 std::string pickedId;
152 for (const auto& primitive : gizmo.primitives) {
153 if (primitive.kind != "sphere" || primitive.id.rfind("spline.", 0) != 0) continue;
154 const std::array<double, 3> offset{origin[0] - primitive.position[0], origin[1] - primitive.position[1],
155 origin[2] - primitive.position[2]};
156 const double b = offset[0] * direction[0] + offset[1] * direction[1] + offset[2] * direction[2];
157 const double c =
158 offset[0] * offset[0] + offset[1] * offset[1] + offset[2] * offset[2] - primitive.radius * primitive.radius;
159 const double discriminant = b * b - c;
160 if (discriminant < 0.0) continue;
161 const double nearHit = -b - std::sqrt(discriminant);
162 const double farHit = -b + std::sqrt(discriminant);
163 const double hit = nearHit >= 0.0 ? nearHit : farHit;
164 if (hit < 0.0 || hit >= closest) continue;
165 closest = hit;
166 pickedId = primitive.id;
167 dragPlanePoint_ = primitive.position;
168 }
169 if (pickedId.empty())
170 return eve::editing::failed<SplinePathDragPreview>(
171 EditorStatus::NotFound, RuleId("editor.spline.handle-not-hit"), "Pointer ray did not hit a spline handle");
172 const auto roleEnd = pickedId.find('.', 7);
173 activeHandle_ = pickedId.substr(7, roleEnd - 7);
174 activePoint_ = StableId(pickedId.substr(roleEnd + 1));
175 const auto points = document_->points();
176 const auto found =
177 std::find_if(points.begin(), points.end(), [&](const auto& point) { return point.id == activePoint_; });
178 if (found == points.end())
179 return eve::editing::failed<SplinePathDragPreview>(
180 EditorStatus::NotFound, RuleId("editor.spline.point-not-found"), "Picked spline point no longer exists");
181 draft_ = *found;
182 dragPlaneNormal_ = direction;
183 baseRevision_ = document_->revision();
184 dragging_ = true;
185 return previewCurrent();
186}
187
189 double rayOriginZ, double rayDirectionX,
190 double rayDirectionY, double rayDirectionZ) {
191 if (!dragging_ || !document_)
192 return eve::editing::failed<SplinePathDragPreview>(
193 EditorStatus::Rejected, RuleId("editor.spline.drag-not-active"), "Spline drag has not begun");
194 if (document_->revision() != baseRevision_) {
195 cancelDrag();
196 return eve::editing::failed<SplinePathDragPreview>(EditorStatus::Conflict, RuleId("editor.spline.drag-stale"),
197 "Spline document changed while dragging");
198 }
199 std::array<double, 3> direction;
200 if (!normalizeRay(rayDirectionX, rayDirectionY, rayDirectionZ, direction))
201 return eve::editing::failed<SplinePathDragPreview>(EditorStatus::Rejected,
202 RuleId("editor.spline.drag-ray-invalid"),
203 "Spline drag requires a finite non-zero pointer ray");
204 const std::array<double, 3> origin{rayOriginX, rayOriginY, rayOriginZ};
205 const double denominator =
206 direction[0] * dragPlaneNormal_[0] + direction[1] * dragPlaneNormal_[1] + direction[2] * dragPlaneNormal_[2];
207 if (std::abs(denominator) < 1e-9)
208 return eve::editing::failed<SplinePathDragPreview>(EditorStatus::Rejected,
209 RuleId("editor.spline.drag-ray-parallel"),
210 "Pointer ray is parallel to the spline drag plane");
211 const double distance = ((dragPlanePoint_[0] - origin[0]) * dragPlaneNormal_[0] +
212 (dragPlanePoint_[1] - origin[1]) * dragPlaneNormal_[1] +
213 (dragPlanePoint_[2] - origin[2]) * dragPlaneNormal_[2]) /
214 denominator;
215 if (distance < 0.0)
216 return eve::editing::failed<SplinePathDragPreview>(EditorStatus::Rejected,
217 RuleId("editor.spline.drag-behind-camera"),
218 "Spline drag plane is behind the pointer ray origin");
219 const std::array<double, 3> world{origin[0] + direction[0] * distance, origin[1] + direction[1] * distance,
220 origin[2] + direction[2] * distance};
221 if (activeHandle_ == "anchor") {
222 draft_.x = world[0];
223 draft_.y = world[1];
224 draft_.z = world[2];
225 } else if (activeHandle_ == "in") {
226 draft_.inX = world[0] - draft_.x;
227 draft_.inY = world[1] - draft_.y;
228 draft_.inZ = world[2] - draft_.z;
229 } else {
230 draft_.outX = world[0] - draft_.x;
231 draft_.outY = world[1] - draft_.y;
232 draft_.outZ = world[2] - draft_.z;
233 }
234 return previewCurrent();
235}
236
238 if (!dragging_ || !document_)
239 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.spline.drag-not-active"),
240 "Spline drag has not begun");
241 if (document_->revision() != baseRevision_) {
242 cancelDrag();
243 return eve::editing::failed<DomainOperation>(EditorStatus::Conflict, RuleId("editor.spline.drag-stale"),
244 "Spline document changed before drag commit");
245 }
246 auto operation = document_->makeSetPoint(draft_);
247 if (operation.ok()) cancelDrag();
248 return operation;
249}
250
252 dragging_ = false;
253 activePoint_ = {};
254 activeHandle_.clear();
255}
256
257} // namespace eve::procgen_editing
ActionParameterOperation operation
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string output
std::string from
float length
Definition CaveMesh.cpp:94
Vec3 anchor
Definition CaveMesh.cpp:90
std::string label
std::string message
std::int32_t c
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
size_t offset
std::array< float, 3 > position
MeleePoint3 b
Definition MeleeHit.cpp:41
float distance
graphics::Canvas * previous
World3D * world
std::string path
Definition PlayHost.cpp:110
std::shared_ptr< const std::vector< glm::vec2 > > points
bool hit
V3 origin
Definition RoadBake.cpp:138
RoadLaneDirection direction
bool found
double current
glm::vec3 point
bool ok() const noexcept
Whether this result represents a non-failure outcome.
Definition Result.h:480
Move-only operation result carrying either a value or Status.
Definition Result.h:155
const std::string & value() const noexcept
Returns the exact legacy or canonical compatibility spelling.
Definition Identity.h:362
std::uint64_t revision() const override
Return the current revision.
Owning spline authoring document with transactional operations and schema-versioned snapshots.
TargetId targetId() const override
Target id.
Result< DomainOperation > makeSetPoint(const SplinePathControlPoint &point) const override
Make set point.
std::vector< SplinePathControlPoint > points() const
Return points in deterministic order and stable-id tie order.
Result< void > applyDomainOperation(const DomainOperation &operation) override
Applies domain operation.
TargetDescriptor describe() const override
Describe.
Result< procgen::SplinePath > compilePath() const
Compile an owning runtime spline without retaining document references.
const SplinePathSettings & settings() const noexcept
Return current interpolation and closure settings.
Result< SplinePathDragPreview > updateDrag(double rayOriginX, double rayOriginY, double rayOriginZ, double rayDirectionX, double rayDirectionY, double rayDirectionZ)
Project a pointer ray onto the original camera-facing drag plane and update only the draft.
void cancelDrag()
Discard transient state without changing the document.
Result< DomainOperation > finishDrag()
Produce one reversible point replacement operation without applying it.
SplinePathDragSession(SplinePathDocument *document, int sampleCount=64)
Create an idle session borrowing an authoritative spline document.
Result< SplinePathDragPreview > beginDrag(double rayOriginX, double rayOriginY, double rayOriginZ, double rayDirectionX, double rayDirectionY, double rayDirectionZ)
Pick the nearest anchor or Bezier handle sphere intersected by a world ray.
Builds sampled path, anchor and Bezier handle overlay primitives.
editing::GizmoSnapshot build(const SplinePathDocument &document, int sampleCount=64, int maximumSamples=4096, SplineGizmoStyle style={}) const
Build an immutable overlay with bounded path sampling.
StrongId< StableIdTag > StableId
Definition EditingIds.h:64
StrongId< RuleIdTag > RuleId
Definition EditingIds.h:60
Diagnostic ruleDiagnostic(eve::DiagnosticCode code, RuleId rule, DiagnosticSeverity severity, std::string message)
Build a common diagnostic carrying an open editing rule identity.
Renderer-neutral wire primitive for authoring viewport overlays.
Immutable revision-tagged overlay snapshot consumed by any viewport renderer.
std::vector< GizmoPrimitive > primitives
std::vector< Diagnostic > diagnostics
Renderer-neutral appearance settings for spline viewport overlays.
Immutable renderer-neutral spline drag preview.
glm::vec4 color