载入中...
搜索中...
未找到
PrimitiveScene.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <atomic>
5#include <cmath>
6#include <limits>
7#include <type_traits>
8#include <utility>
9
10namespace eve::graphics {
11namespace {
12
13bool finite(glm::vec3 value) { return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z); }
14
15bool nonZero(glm::vec3 value) {
16 constexpr float minimumLengthSquared = 1e-12f;
17 return finite(value) && glm::dot(value, value) > minimumLengthSquared;
18}
19
20bool independent(glm::vec3 a, glm::vec3 b) {
21 constexpr float minimumRelativeAreaSquared = 1e-12f;
22 const float aLengthSquared = glm::dot(a, a);
23 const float bLengthSquared = glm::dot(b, b);
24 const glm::vec3 crossValue = glm::cross(a, b);
25 const float areaSquared = glm::dot(crossValue, crossValue);
26 return aLengthSquared > 0.f && bLengthSquared > 0.f &&
27 areaSquared > minimumRelativeAreaSquared * aLengthSquared * bLengthSquared;
28}
29
30bool formsBasis(glm::vec3 a, glm::vec3 b, glm::vec3 c) {
31 constexpr float minimumRelativeVolumeSquared = 1e-12f;
32 const float volume = glm::dot(a, glm::cross(b, c));
33 return volume * volume > minimumRelativeVolumeSquared * glm::dot(a, a) * glm::dot(b, b) * glm::dot(c, c);
34}
35
36eve::Result<void> validateDescriptor(const PrimitiveDescriptor3D& descriptor) {
37 const auto& paint = descriptor.paint;
38 const auto& stroke = paint.stroke;
39 if (!std::isfinite(stroke.width) || stroke.width < 0.f ||
40 !std::isfinite(stroke.miterLimit) || stroke.miterLimit <= 0.f ||
41 !std::isfinite(paint.color.r) || !std::isfinite(paint.color.g) ||
42 !std::isfinite(paint.color.b) || !std::isfinite(paint.color.a))
44 "primitive paint must be finite and valid",
45 "primitive", {}, "graphics"));
46 if (stroke.dash) {
47 const auto& dash = *stroke.dash;
48 float period = 0.f;
49 if (dash.intervals.empty() || dash.intervals.size() % 2 != 0 || !std::isfinite(dash.phase))
51 eve::DiagnosticCode::InvalidArgument, "invalid primitive dash pattern", "primitive", {}, "graphics"));
52 for (float interval : dash.intervals) {
53 if (!std::isfinite(interval) || interval <= 0.f)
55 "invalid primitive dash interval", "primitive",
56 {}, "graphics"));
57 period += interval;
58 }
59 if (!std::isfinite(period))
61 eve::DiagnosticCode::InvalidArgument, "primitive dash period overflow", "primitive", {}, "graphics"));
62 }
63 try {
64 descriptor.paint.validate();
65 } catch (const std::exception& error) {
67 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, error.what(), "primitive", {}, "graphics"));
68 }
69 for (glm::length_t column = 0; column < 4; ++column)
70 for (glm::length_t row = 0; row < 4; ++row)
71 if (!std::isfinite(descriptor.transform[column][row]))
73 "primitive transform must be finite",
74 "primitive", {}, "graphics"));
75 const bool valid = std::visit(
76 [](const auto& geometry) {
77 using Geometry = std::decay_t<decltype(geometry)>;
78 if constexpr (std::is_same_v<Geometry, PrimitivePolyline3D>) {
79 return geometry.points.size() >= (geometry.closed ? 3u : 2u) &&
80 std::all_of(geometry.points.begin(), geometry.points.end(), finite);
81 } else if constexpr (std::is_same_v<Geometry, PrimitiveAabb3D>) {
82 return finite(geometry.minimum) && finite(geometry.maximum) &&
83 geometry.minimum.x <= geometry.maximum.x && geometry.minimum.y <= geometry.maximum.y &&
84 geometry.minimum.z <= geometry.maximum.z;
85 } else if constexpr (std::is_same_v<Geometry, PrimitiveObb3D>) {
86 return finite(geometry.center) && nonZero(geometry.halfAxes[0]) && nonZero(geometry.halfAxes[1]) &&
87 nonZero(geometry.halfAxes[2]) && independent(geometry.halfAxes[0], geometry.halfAxes[1]) &&
88 independent(geometry.halfAxes[1], geometry.halfAxes[2]) &&
89 independent(geometry.halfAxes[2], geometry.halfAxes[0]) &&
90 formsBasis(geometry.halfAxes[0], geometry.halfAxes[1], geometry.halfAxes[2]);
91 } else if constexpr (std::is_same_v<Geometry, PrimitiveDisk3D>) {
92 return finite(geometry.center) && nonZero(geometry.normal) && std::isfinite(geometry.radius) &&
93 geometry.radius > 0.f && geometry.segments >= 3;
94 } else if constexpr (std::is_same_v<Geometry, PrimitiveArc3D>) {
95 return finite(geometry.center) && nonZero(geometry.normal) && nonZero(geometry.zeroDirection) &&
96 independent(geometry.normal, geometry.zeroDirection) &&
97 std::isfinite(geometry.radius) && geometry.radius > 0.f &&
98 std::isfinite(geometry.startRadians) && std::isfinite(geometry.sweepRadians) &&
99 geometry.segments >= 1;
100 } else if constexpr (std::is_same_v<Geometry, PrimitiveSphere3D>) {
101 return finite(geometry.center) && std::isfinite(geometry.radius) && geometry.radius > 0.f &&
102 geometry.segments >= 3;
103 } else if constexpr (std::is_same_v<Geometry, PrimitiveCapsule3D> ||
104 std::is_same_v<Geometry, PrimitiveCylinder3D>) {
105 return finite(geometry.a) && finite(geometry.b) && nonZero(geometry.b - geometry.a) &&
106 std::isfinite(geometry.radius) &&
107 geometry.radius > 0.f && geometry.segments >= 3;
108 } else if constexpr (std::is_same_v<Geometry, PrimitiveCone3D>) {
109 return finite(geometry.apex) && nonZero(geometry.axis) && std::isfinite(geometry.height) &&
110 geometry.height > 0.f && std::isfinite(geometry.radius) && geometry.radius > 0.f &&
111 geometry.segments >= 3;
112 } else if constexpr (std::is_same_v<Geometry, PrimitiveGrid3D>) {
113 return finite(geometry.origin) && nonZero(geometry.axisU) && nonZero(geometry.axisV) &&
114 independent(geometry.axisU, geometry.axisV) &&
115 geometry.cellsU > 0 && geometry.cellsV > 0;
116 } else if constexpr (std::is_same_v<Geometry, PrimitiveArrow3D>) {
117 return finite(geometry.from) && finite(geometry.to) && nonZero(geometry.to - geometry.from) &&
118 std::isfinite(geometry.headLength) &&
119 geometry.headLength > 0.f && std::isfinite(geometry.headRadius) && geometry.headRadius > 0.f;
120 } else {
121 return std::all_of(geometry.corners.begin(), geometry.corners.end(), finite);
122 }
123 },
124 descriptor.geometry);
125 if (!valid)
127 "primitive geometry is invalid or non-finite",
128 "primitive", {}, "graphics"));
130}
131
132} // namespace
133
135 static std::atomic<PrimitiveHandle::owner_type> nextOwner{1};
136 owner_ = nextOwner.fetch_add(1, std::memory_order_relaxed);
137 if (owner_ == 0) owner_ = nextOwner.fetch_add(1, std::memory_order_relaxed);
138}
139
141 auto validation = validateDescriptor(descriptor);
142 if (!validation) return eve::Result<PrimitiveHandle>::failure(validation.status());
144 if (!freeSlots_.empty()) {
145 index = freeSlots_.back();
146 freeSlots_.pop_back();
147 slots_[index].descriptor = std::move(descriptor);
148 } else {
149 if (slots_.size() >= PrimitiveHandle::invalidIndex) {
151 eve::DiagnosticCode::Failed, "primitive scene slot capacity exceeded", "primitive", {}, "graphics"));
152 }
153 index = static_cast<PrimitiveHandle::index_type>(slots_.size());
154 slots_.push_back(Slot{std::move(descriptor), 1, false, {}});
155 }
156 ++liveCount_;
158}
159
161 if (!matches(handle)) {
163 eve::DiagnosticCode::StaleHandle, "primitive handle is stale", "primitive", {}, "graphics"));
164 }
165 auto validation = validateDescriptor(descriptor);
166 if (!validation) return eve::Result<PrimitiveUpdateStatus>::failure(validation.status());
167 slots_[handle.index()].descriptor = std::move(descriptor);
168 slots_[handle.index()].cache.reset();
170}
171
173 if (!matches(handle)) {
175 eve::DiagnosticCode::StaleHandle, "primitive handle is stale", "primitive", {}, "graphics"));
176 }
177 Slot& slot = slots_[handle.index()];
178 slot.descriptor.reset();
179 slot.cache.reset();
180 --liveCount_;
181 const auto next = PrimitiveHandle::nextGeneration(slot.generation);
182 if (next) {
183 slot.generation = *next;
184 freeSlots_.push_back(handle.index());
185 } else {
186 slot.retired = true;
187 }
189}
190
191eve::Result<std::size_t> PrimitiveScene::updateMany(std::span<const PrimitiveBatchUpdate> updates) {
192 for (const PrimitiveBatchUpdate& update : updates) {
193 if (!matches(update.handle))
195 "primitive batch contains a stale handle",
196 "primitive", {}, "graphics"));
197 auto validation = validateDescriptor(update.descriptor);
198 if (!validation) return eve::Result<std::size_t>::failure(validation.status());
199 }
200 // Copy every owning payload before publishing any mutation. A later allocation
201 // failure must not leave earlier descriptors in the batch already replaced.
202 std::vector<PrimitiveBatchUpdate> prepared(updates.begin(), updates.end());
203 static_assert(std::is_nothrow_move_assignable_v<PrimitiveDescriptor3D>);
204 for (PrimitiveBatchUpdate& update : prepared) {
205 *slots_[update.handle.index()].descriptor = std::move(update.descriptor);
206 slots_[update.handle.index()].cache.reset();
207 }
208 return eve::Result<std::size_t>::success(updates.size());
209}
210
212 if (!matches(handle)) return nullptr;
213 return &*slots_[handle.index()].descriptor;
214}
215
216bool PrimitiveScene::isStale(PrimitiveHandle handle) const noexcept { return !matches(handle); }
217
219 freeSlots_.clear();
220 liveCount_ = 0;
221 for (PrimitiveHandle::index_type index = 0; index < slots_.size(); ++index) {
222 Slot& slot = slots_[index];
223 slot.descriptor.reset();
224 slot.cache.reset();
225 if (slot.retired) continue;
226 const auto next = PrimitiveHandle::nextGeneration(slot.generation);
227 if (next) {
228 slot.generation = *next;
229 freeSlots_.push_back(index);
230 } else {
231 slot.retired = true;
232 }
233 }
234}
235
237 auto result = tryRender(destination);
238 std::move(result).expect("primitive scene exceeded its command budget");
239}
240
242 for (const Slot& slot : slots_) {
243 if (!slot.descriptor || !slot.descriptor->visible) continue;
244 const PrimitiveDescriptor3D& descriptor = *slot.descriptor;
245 const bool hit = slot.cache.has_value();
246 if (!hit) {
247 PrimitiveSceneCanvas3D canvas(destination.context_, std::numeric_limits<std::size_t>::max());
248 canvas.concat(descriptor.transform);
249 std::visit(
250 [&](const auto& geometry) {
251 using Geometry = std::decay_t<decltype(geometry)>;
252 if constexpr (std::is_same_v<Geometry, PrimitivePolyline3D>) {
253 canvas.drawPolyline(geometry.points, geometry.closed, descriptor.paint);
254 } else if constexpr (std::is_same_v<Geometry, PrimitiveAabb3D>) {
255 canvas.drawAabb(geometry.minimum, geometry.maximum, descriptor.paint);
256 } else if constexpr (std::is_same_v<Geometry, PrimitiveObb3D>) {
257 canvas.drawObb(geometry.center, geometry.halfAxes, descriptor.paint);
258 } else if constexpr (std::is_same_v<Geometry, PrimitiveDisk3D>) {
259 canvas.drawDisk(geometry.center, geometry.normal, geometry.radius, descriptor.paint,
260 geometry.segments);
261 } else if constexpr (std::is_same_v<Geometry, PrimitiveArc3D>) {
262 canvas.drawArc(geometry.center, geometry.normal, geometry.zeroDirection, geometry.radius,
263 geometry.startRadians, geometry.sweepRadians, descriptor.paint,
264 geometry.segments);
265 } else if constexpr (std::is_same_v<Geometry, PrimitiveSphere3D>) {
266 canvas.drawSphere(geometry.center, geometry.radius, descriptor.paint, geometry.segments);
267 } else if constexpr (std::is_same_v<Geometry, PrimitiveCapsule3D>) {
268 canvas.drawCapsule(geometry.a, geometry.b, geometry.radius, descriptor.paint,
269 geometry.segments);
270 } else if constexpr (std::is_same_v<Geometry, PrimitiveCylinder3D>) {
271 canvas.drawCylinder(geometry.a, geometry.b, geometry.radius, descriptor.paint,
272 geometry.segments);
273 } else if constexpr (std::is_same_v<Geometry, PrimitiveCone3D>) {
274 canvas.drawCone(geometry.apex, geometry.axis, geometry.height, geometry.radius,
275 descriptor.paint, geometry.segments);
276 } else if constexpr (std::is_same_v<Geometry, PrimitiveGrid3D>) {
277 canvas.drawGrid(geometry.origin, geometry.axisU, geometry.axisV, geometry.cellsU,
278 geometry.cellsV, descriptor.paint);
279 } else if constexpr (std::is_same_v<Geometry, PrimitiveArrow3D>) {
280 canvas.drawArrow(geometry.from, geometry.to, geometry.headLength, geometry.headRadius,
281 descriptor.paint);
282 } else if constexpr (std::is_same_v<Geometry, PrimitiveFrustum3D>) {
283 canvas.drawFrustum(geometry.corners, descriptor.paint);
284 }
285 },
286 descriptor.geometry);
287 slot.cache = std::move(canvas);
288 }
289 const auto& cached = *slot.cache;
290 const auto count = cached.commands_.size() + cached.triangles_.size();
291 const auto used = destination.commands_.size() + destination.triangles_.size();
292 if (used > destination.hardCommandLimit_ || count > destination.hardCommandLimit_ - used) {
293 destination.statistics_.droppedCommands += count + cached.statistics_.droppedCommands;
295 eve::DiagnosticCode::Failed, "primitive scene command budget exceeded", "primitive", {}, "graphics"));
296 }
297 // Prepare all owning payloads and capacity before publishing this slot.
298 auto lines = cached.commands_;
299 auto triangles = cached.triangles_;
300 for (auto& command : lines) command.transform = destination.transform_ * command.transform;
301 for (auto& command : triangles) command.transform = destination.transform_ * command.transform;
302 destination.commands_.reserve(destination.commands_.size() + lines.size());
303 destination.triangles_.reserve(destination.triangles_.size() + triangles.size());
304 static_assert(std::is_nothrow_move_constructible_v<PolylineCommand3D>);
305 static_assert(std::is_nothrow_move_constructible_v<TriangleCommand3D>);
306 for (auto& command : lines) destination.commands_.push_back(std::move(command));
307 for (auto& command : triangles) destination.triangles_.push_back(std::move(command));
308 destination.statistics_.commandCount += cached.statistics_.commandCount;
309 destination.statistics_.segmentCount += cached.statistics_.segmentCount;
310 destination.statistics_.triangleCount += cached.statistics_.triangleCount;
311 destination.statistics_.cacheHits += hit ? 1u : 0u;
312 }
314}
315
316bool PrimitiveScene::matches(PrimitiveHandle handle) const noexcept {
317 if (!handle.isValid() || handle.owner() != owner_ || handle.index() >= slots_.size()) return false;
318 const Slot& slot = slots_[handle.index()];
319 return !slot.retired && slot.descriptor.has_value() && slot.generation == handle.generation();
320}
321
322} // namespace eve::graphics
double value
std::string descriptor
double volume
int column
float u
Definition Grass.cpp:233
std::int32_t c
bool valid
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::vector< TriangleRef > triangles
bool finite
std::string error
Definition Package.cpp:60
std::uint32_t generation
bool hit
PrimitiveHandle handle
std::uint32_t count
uint32_t index
std::vector< std::string_view > lines
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
Instance-qualified persistent primitive handle.
constexpr generation_type generation() const noexcept
Generation.
constexpr bool isValid() const noexcept
True when valid.
LocalHandle::index_type index_type
constexpr owner_type owner() const noexcept
Owner.
static constexpr index_type invalidIndex
constexpr index_type index() const noexcept
Index.
static constexpr std::optional< generation_type > nextGeneration(generation_type current) noexcept
Next generation.
Frame-local scene recorder for 3D line primitives.
void drawArrow(glm::vec3 from, glm::vec3 to, float headLength, float headRadius, const ScenePrimitivePaint &paint)
Records a shaft and four-sided arrow head.
void drawSphere(glm::vec3 center, float radius, const ScenePrimitivePaint &paint, std::uint32_t segments=32)
Records three orthogonal great circles.
void concat(const glm::mat4 &transform)
Concat.
void drawCapsule(glm::vec3 a, glm::vec3 b, float radius, const ScenePrimitivePaint &paint, std::uint32_t segments=32)
Draws capsule.
void drawFrustum(const std::array< glm::vec3, 8 > &corners, const ScenePrimitivePaint &paint)
Draws frustum.
void drawObb(glm::vec3 center, const std::array< glm::vec3, 3 > &halfAxes, const ScenePrimitivePaint &paint)
Draws obb.
void drawGrid(glm::vec3 origin, glm::vec3 axisU, glm::vec3 axisV, std::uint32_t cellsU, std::uint32_t cellsV, const ScenePrimitivePaint &paint)
Records a planar grid spanned by two caller-provided axes.
void drawAabb(glm::vec3 minimum, glm::vec3 maximum, const ScenePrimitivePaint &paint)
Records the twelve edges of an axis-aligned box.
void drawPolyline(std::span< const glm::vec3 > points, bool closed, const ScenePrimitivePaint &paint)
Draws polyline.
void drawArc(glm::vec3 center, glm::vec3 normal, glm::vec3 zeroDirection, float radius, float startRadians, float sweepRadians, const ScenePrimitivePaint &paint, std::uint32_t segments=32)
Draws arc.
void drawCylinder(glm::vec3 a, glm::vec3 b, float radius, const ScenePrimitivePaint &paint, std::uint32_t segments=32)
Records endpoint circles and four side lines of a cylinder.
void drawDisk(glm::vec3 center, glm::vec3 normal, float radius, const ScenePrimitivePaint &paint, std::uint32_t segments=32)
Draws disk.
void drawCone(glm::vec3 apex, glm::vec3 axis, float height, float radius, const ScenePrimitivePaint &paint, std::uint32_t segments=32)
Records the base circle and four side lines of a cone.
void clear()
Invalidates all live handles while retaining slot capacity.
void render(PrimitiveSceneCanvas3D &canvas) const
Compatibility-only wrapper over tryRender; throws on budget failure.
eve::Result< PrimitiveRemoveStatus > remove(PrimitiveHandle handle)
Removes .
eve::Result< PrimitiveHandle > add(PrimitiveDescriptor3D descriptor)
Adds .
eve::Result< std::size_t > updateMany(std::span< const PrimitiveBatchUpdate > updates)
Atomically replaces several live descriptors after validating every handle.
eve::Result< PrimitiveUpdateStatus > update(PrimitiveHandle handle, PrimitiveDescriptor3D descriptor)
Updates .
eve::Result< void > tryRender(PrimitiveSceneCanvas3D &canvas) const
Projects visible descriptors in stable slot order using per-slot CPU caches. Successful updates inval...
const PrimitiveDescriptor3D * tryGet(PrimitiveHandle handle) const noexcept
Resolves a live primitive handle.
bool isStale(PrimitiveHandle handle) const noexcept
Returns true when a handle is invalid, removed, foreign, or generation-stale.
卡牌游戏 UI 工具模块:工厂 + 脚本绑定入口。 功能参考 ycarowr/UiCard:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
Definition Animation.h:25
WidgetDesc row(std::vector< WidgetDesc > children, std::string id)
Horizontal elastic layout row.
Definition Widget.cpp:679
PrimitiveBatchUpdate public API.
One persistent scene primitive owned by PrimitiveScene.