16 const auto found = std::find_if(entries_.begin(), entries_.end(),
17 [probe](
const Entry &entry) { return entry.probe == probe; });
18 if (
found == entries_.end()) {
19 entries_.push_back(Entry{probe, nextOrder_++});
20 probe->attachRegistry(
this);
25 if (probe) probe->detachRegistry(
this);
26 entries_.erase(std::remove_if(entries_.begin(), entries_.end(),
27 [probe](
const Entry &entry) { return entry.probe == probe; }),
29 lastSelected_.erase(std::remove(lastSelected_.begin(), lastSelected_.end(), probe),
34 for (
const Entry &entry : entries_)
35 if (entry.probe) entry.probe->detachRegistry(
this);
37 lastSelected_.clear();
38 lastSelectedCount_ = 0;
39 lastCapturedFaceCount_ = 0;
40 lastPublishedCount_ = 0;
41 lastCandidateCount_ = 0;
42 lastDistanceCulledCount_ = 0;
48 "Reflection probe render distance must be positive and finite",
49 "graphics.reflectionProbe.maxRenderDistance"));
55 selectionHysteresis_ = std::clamp(
distance, 0.f, 100.f);
59 const uint32_t
mask =
static_cast<uint32_t
>(changedMask);
61 for (Entry &entry : entries_) {
62 if (!entry.probe || (
static_cast<uint32_t
>(entry.probe->getCaptureMask()) &
mask) == 0
u)
64 entry.probe->queueCapture();
71 float maxY,
float maxZ,
int changedMask) {
72 const float changedMin[3] = {std::min(minX, maxX), std::min(minY, maxY),
73 std::min(minZ, maxZ)};
74 const float changedMax[3] = {std::max(minX, maxX), std::max(minY, maxY),
75 std::max(minZ, maxZ)};
76 const uint32_t
mask =
static_cast<uint32_t
>(changedMask);
78 for (Entry &entry : entries_) {
82 float distance2 = 0.f;
83 for (
int axis = 0; axis < 3; ++axis) {
84 const float nearest = std::clamp(
center[axis], changedMin[axis], changedMax[axis]);
85 const float delta =
center[axis] - nearest;
86 distance2 += delta * delta;
89 if (distance2 > captureRadius * captureRadius)
continue;
97 lastCapturedFaceCount_ = 0;
98 lastPublishedCount_ = 0;
99 faceBudget = std::max(faceBudget, 0);
100 filterBudget = std::max(filterBudget, 0);
101 filterSamples = std::clamp(filterSamples, 8, 512);
103 for (Entry &entry : entries_) {
104 if (!entry.probe || (distanceCullingEnabled_ && entry.distanceCulled))
continue;
105 entry.probe->advanceRefreshPolicy();
106 const bool waiting = entry.probe->isCapturePending() ||
107 entry.probe->getPublishedRevision() != entry.probe->getRevision();
108 if (waiting && entry.waitAge < std::numeric_limits<uint32_t>::max()) ++entry.waitAge;
111 auto better = [](
const Entry *
a,
const Entry *
b) {
112 const int priorityA =
a->probe->getInfluencePriority();
113 const int priorityB =
b->probe->getInfluencePriority();
114 if (priorityA != priorityB)
return priorityA > priorityB;
115 if (
a->waitAge !=
b->waitAge)
return a->waitAge >
b->waitAge;
116 return a->order <
b->order;
119 while (faceBudget-- > 0) {
120 Entry *selected =
nullptr;
121 for (Entry &entry : entries_) {
122 if (!entry.probe || !entry.probe->isCapturePending())
continue;
123 if (!selected || better(&entry, selected)) selected = &entry;
125 if (!selected)
break;
126 selected->probe->update(1);
127 selected->waitAge = 0;
128 lastCapturedFaceCount_ += selected->probe->getLastCapturedFaceCount();
131 while (filterBudget-- > 0) {
132 Entry *selected =
nullptr;
133 for (Entry &entry : entries_) {
134 if (!entry.probe || entry.probe->isCapturePending() ||
135 entry.probe->getRevision() == entry.probe->getPublishedRevision())
137 if (!selected || better(&entry, selected)) selected = &entry;
139 if (!selected)
break;
140 if (selected->probe->stageCapturedFaces() &&
141 selected->probe->filterAndPublish(filterSamples)) {
142 selected->waitAge = 0;
143 ++lastPublishedCount_;
148 return lastPublishedCount_;
152 lastSelectedCount_ = 0;
153 lastCandidateCount_ = 0;
154 lastDistanceCulledCount_ = 0;
156 lastSelected_.clear();
160 camera->clearReflectionProbe(slot);
165 float distance2 = 0.f;
166 bool retained =
false;
169 std::vector<Candidate> candidates;
170 candidates.reserve(entries_.size());
171 const auto cameraData =
camera->data();
172 const float eye[3] = {cameraData->eyeX, cameraData->eyeY, cameraData->eyeZ};
173 const float target[3] = {cameraData->targetX, cameraData->targetY, cameraData->targetZ};
176 const float viewSegmentLength2 = viewSegment[0] * viewSegment[0] +
177 viewSegment[1] * viewSegment[1] +
178 viewSegment[2] * viewSegment[2];
179 for (Entry &entry : entries_) {
181 if (!probe)
continue;
184 const bool distanceCulled = distanceCullingEnabled_ &&
185 eyeDx * eyeDx + eyeDy * eyeDy + eyeDz * eyeDz >= maxRenderDistance_ * maxRenderDistance_;
186 if (entry.distanceCulled && !distanceCulled) probe->
queueCapture();
187 entry.distanceCulled = distanceCulled;
188 if (distanceCulled) { ++lastDistanceCulledCount_;
continue; }
194 float closestT = 0.f;
195 if (viewSegmentLength2 > 1e-8f) {
198 closestT = std::clamp((toCenter[0] * viewSegment[0] +
199 toCenter[1] * viewSegment[1] +
200 toCenter[2] * viewSegment[2]) /
204 const float closestViewPoint[3] = {
205 eye[0] + viewSegment[0] * closestT,
206 eye[1] + viewSegment[1] * closestT,
207 eye[2] + viewSegment[2] * closestT,
209 float distance2 = 0.f;
210 for (
int axis = 0; axis < 3; ++axis) {
211 const float outside =
212 std::max(std::abs(closestViewPoint[axis] -
center[axis]) - extent[axis], 0.f);
213 distance2 += outside * outside;
215 const bool retained =
216 std::find(lastSelected_.begin(), lastSelected_.end(), probe) != lastSelected_.end();
217 const float distance = std::sqrt(distance2);
218 const float biasedDistance =
221 biasedDistance * biasedDistance, retained, entry.order});
223 lastCandidateCount_ =
static_cast<int>(candidates.size());
224 std::stable_sort(candidates.begin(), candidates.end(),
225 [](
const Candidate &
a,
const Candidate &
b) {
226 if (a.priority != b.priority) return a.priority > b.priority;
227 if (a.distance2 != b.distance2) return a.distance2 < b.distance2;
228 if (a.retained != b.retained) return a.retained;
229 return a.order < b.order;
232 const int selected = std::min(
static_cast<int>(candidates.size()),
234 lastSelected_.clear();
235 lastSelected_.reserve(
static_cast<size_t>(selected));
236 for (
int slot = 0; slot < selected; ++slot) {
239 lastSelected_.push_back(probe);
240 ++lastSelectedCount_;
243 return lastSelectedCount_;
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.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
EVENGINE_API_BACKENDS public API.
Incremental six-face HDR scene capture for a runtime reflection probe.
float getInfluenceExtentY() const
Return persistent influence half extent Y.
float getCaptureFarDistance() const
Return the capture far distance used for spatial dirty culling.
bool applyConfiguredToCamera(Camera3D *camera, int slot)
Submit the persistent influence and active cubemap to a Camera3D slot. @compatibility Preserves the s...
float getInfluenceExtentX() const
Return persistent influence half extent X.
float getCenterY() const
Return probe world-space center Y.
uint32_t getPublishedRevision() const
Last capture revision atomically published for lighting.
int getInfluencePriority() const
Return persistent overlap-selection priority.
float getInfluenceExtentZ() const
Return persistent influence half extent Z.
float getInfluenceIntensity() const
Return persistent reflection intensity.
void queueCapture()
Queue a coherent recapture without restarting a revision already in progress.
float getCenterZ() const
Return probe world-space center Z.
int getCaptureMask() const
Return the object-layer mask visible to this reflection capture.
float getCenterX() const
Return probe world-space center X.
Texture * getActiveCubemap() const
Last fully filtered cubemap, safe to assign to Camera3D reflection probes. @lifetime Returned texture...
int tick(int faceBudget=1, int filterBudget=1, int filterSamples=64)
Advance every refresh policy while enforcing scene-wide GPU work budgets.
~ReflectionProbeRegistry()
Detach all probes before script-owned destruction.
void remove(ReflectionProbeCapture *probe)
Remove a registered probe.
Result< void > setMaxRenderDistance(float distance)
Set the positive Pcg probe render distance; failure preserves the previous value.
void setSelectionHysteresis(float distance)
Set world-space distance hysteresis used to retain previous camera selections.
int queueCaptureAABB(float minX, float minY, float minZ, float maxX, float maxY, float maxZ, int changedMask=-1)
Queue probes whose mask and capture-radius sphere overlap a changed world AABB.
void clear()
Remove every registered probe.
int queueCapture(int changedMask=-1)
Queue coherent recapture for probes whose capture mask intersects changedMask.
int updateCamera(Camera3D *camera)
Select eight probes nearest the camera view segment and replace Camera3D slots.
void add(ReflectionProbeCapture *probe)
Register a probe once; duplicate registrations are ignored.
卡牌游戏 UI 工具模块:工厂 + 脚本绑定入口。 功能参考 ycarowr/UiCard:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
static constexpr int kMaxReflectionProbes