载入中...
搜索中...
未找到
ClimbingECS.h
浏览该文件的文档.
1#pragma once
2#include "common/Export.h"
3
4
10#include "climbing/Climbing.h"
12#include "common/ECS.h"
13#include "physics/Body3D.h"
14#include "physics/PhysicsLink.h"
15#include "physics/World3D.h"
16
17#include <array>
18#include <algorithm>
19#include <chrono>
20#include <cmath>
21#include <cstddef>
22#include <cstdint>
23#include <span>
24#include <string>
25#include <string_view>
26#include <utility>
27
28namespace eve::climbing {
29
50
64
67public:
68 static constexpr std::size_t Capacity = ClimbingCandidateSet::Capacity;
69
71 [[nodiscard]] eve::Result<void> replace(std::span<const ClimbingCandidate> candidates,
74 [[nodiscard]] eve::Result<void> probe(ClimbingRuntime& runtime, physics::World3D& world,
77 [[nodiscard]] std::span<const ClimbingCandidate> values() const noexcept {
78 return values_.values();
79 }
81 void clear() noexcept { values_.clear(); }
83 [[nodiscard]] eve::SimulationTick tick() const noexcept { return tick_; }
84
85private:
88};
89
106
119
122public:
123 static constexpr std::size_t Capacity = ClimbingRuntime::PendingEventCapacity;
124
126 [[nodiscard]] eve::Result<void> replace(std::span<const ClimbingEvent> events,
129 [[nodiscard]] std::span<const ClimbingEvent> values() const noexcept { return {values_.data(), size_}; }
131 [[nodiscard]] eve::SimulationTick tick() const noexcept { return tick_; }
132
133private:
134 std::array<ClimbingEvent, Capacity> values_{};
135 std::size_t size_ = 0;
137};
138
141 std::string_view name;
142 std::string_view entityScope;
143 std::string_view view;
144 std::string_view readSet;
145 std::string_view writeSet;
146 std::string_view structuralChanges;
147 std::string_view events;
148 std::string_view services;
149 std::string_view phase;
150 std::string_view determinism;
151};
152
154[[nodiscard]] EVENGINE_API_DOMAINS std::span<const ClimbingSystemContract> climbingSystemContracts() noexcept;
155
156namespace detail {
157
163} // namespace detail
164
167public:
177 template <class EntityRoot>
181 std::size_t processed = 0;
182 auto view = ecs::View<EntityRoot, ClimbingBody, ClimbingIntent, ClimbingState,
184 for (auto it = view.begin(); it != view.end(); ++it) {
185 auto [body, intent, state, links, buffer] = *it;
186 (void)intent;
187 auto runtime = Climbing::resolve(state->runtime);
188 if (!runtime.isBound())
191 "climbing runtime link is stale",
192 "state.runtime", {}, "climbing.ecs"));
193 auto linkedBody = links->physicsBody.resolve(world);
194 if (!linkedBody)
196 return eve::Result<std::size_t>::failure(linkedBody.status());
197 const bool hasEligibleIntent = std::any_of(intent->commands.begin(), intent->commands.end(),
198 [&](const BufferedClimbingCommand& command) {
199 return command.consumedExecutionId.isZero() &&
200 command.pressedTick <= tick && tick <= command.expiryTick;
201 });
202 if (!hasEligibleIntent) {
203 buffer->clear();
204 ++processed;
205 continue;
206 }
207 const ClimbingPose pose{body->feet, body->forward,
209 std::sqrt(body->velocity.x * body->velocity.x +
210 body->velocity.z * body->velocity.z),
211 linkedBody.value()->getId(), body->velocity.y, body->grounded,
212 intent->move, intent->look, intent->mode};
213 auto probed = buffer->probe(*runtime, world, pose, tick);
214 if (!probed) return eve::Result<std::size_t>::failure(probed.status());
215 ++processed;
216 }
218 return eve::Result<std::size_t>::success(processed);
219 }
220};
221
224public:
229 template <class EntityRoot>
232 ClimbingCommand command,
234 std::size_t started = 0;
235 auto view = ecs::View<EntityRoot, ClimbingBody, ClimbingIntent, ClimbingState, ClimbingLinks>();
236 for (auto it = view.begin(); it != view.end(); ++it) {
237 auto [body, intent, state, links] = *it;
238 if (!ClimbingInputSystem::peek(*intent, command, tick)) continue;
239 auto runtime = Climbing::resolve(state->runtime);
240 if (!runtime.isBound())
243 "climbing runtime link is stale",
244 "state.runtime", {}, "climbing.ecs"));
245 auto linkedBody = links->physicsBody.resolve(world);
246 if (!linkedBody) return eve::Result<std::size_t>::failure(linkedBody.status());
247 const ClimbingPose pose{body->feet, body->forward,
249 std::sqrt(body->velocity.x * body->velocity.x +
250 body->velocity.z * body->velocity.z),
251 linkedBody.value()->getId(), body->velocity.y, body->grounded,
252 intent->move, intent->look, intent->mode};
253 auto selected = ClimbingSelectionSystem::tryStart(*runtime, world, pose, *intent, command,
254 tick, body->lastGroundedTick);
255 if (!selected) return eve::Result<std::size_t>::failure(selected.status());
256 ++started;
257 }
260 }
261};
262
265public:
270 template <class EntityRoot>
274 const ClimbingMotionInput& motion = {}) {
275 if (step.delta.nanoseconds() <= 0)
278 "climbing motion delta must be positive",
279 "step.delta", {}, "climbing.ecs"));
280 std::size_t processed = 0;
281 auto view = ecs::View<EntityRoot, ClimbingBody, ClimbingIntent, ClimbingState, ClimbingLinks>();
282 for (auto it = view.begin(); it != view.end(); ++it) {
283 auto [body, intent, state, links] = *it;
284 auto runtime = Climbing::resolve(state->runtime);
285 if (!runtime.isBound())
288 "climbing runtime link is stale",
289 "state.runtime", {}, "climbing.ecs"));
290 auto linkedBody = links->physicsBody.resolve(world);
291 if (!linkedBody) return eve::Result<std::size_t>::failure(linkedBody.status());
292 if (!detail::activeClimbingPhase(runtime->phase())) {
293 const auto ordinaryStart = std::chrono::steady_clock::now();
294 if (!(body->capsuleRadius > 0.f) ||
295 !(body->capsuleHeight > body->capsuleRadius * 2.f) || body->skin < 0.f ||
296 body->groundSnap < 0.f || body->stepHeight < 0.f ||
297 !std::isfinite(body->maxSlopeRadians) || body->maxSlopeRadians < 0.f ||
298 body->maxSlopeRadians >= 1.57079633f)
301 eve::DiagnosticCode::InvalidArgument, "ordinary locomotion capsule is invalid", "body.capsule",
302 {}, "climbing.ecs"));
303 const ClimbingLocomotionPolicy policy = runtime->locomotionPolicy();
304 const float deltaSeconds = static_cast<float>(step.delta.seconds());
305 const float moveLength = std::sqrt(intent->move.x * intent->move.x +
306 intent->move.z * intent->move.z);
307 const Vec3 desiredVelocity{intent->move.x, 0.f, intent->move.z};
308 const float acceleration = body->grounded
309 ? (moveLength > 1e-6f ? policy.groundAcceleration
310 : policy.groundBraking)
311 : policy.groundAcceleration * policy.airControl;
312 const float maxVelocityChange = acceleration * deltaSeconds;
313 const Vec3 horizontalError{desiredVelocity.x - body->velocity.x, 0.f,
314 desiredVelocity.z - body->velocity.z};
315 const float errorLength = std::sqrt(horizontalError.x * horizontalError.x +
316 horizontalError.z * horizontalError.z);
317 if (errorLength > maxVelocityChange && errorLength > 1e-6f) {
318 const float scale = maxVelocityChange / errorLength;
319 body->velocity.x += horizontalError.x * scale;
320 body->velocity.z += horizontalError.z * scale;
321 } else {
322 body->velocity.x = desiredVelocity.x;
323 body->velocity.z = desiredVelocity.z;
324 }
325
326 const auto jump = ClimbingInputSystem::peek(*intent, ClimbingCommand::Jump, step.tick);
327 const bool freshJump = jump && (!body->hasOrdinaryJump ||
328 jump->pressedTick != body->lastOrdinaryJumpPressedTick);
329 if (freshJump &&
330 (body->grounded ||
332 ClimbingInputSystem::coyoteWindowState(step.tick, body->lastGroundedTick,
333 policy.coyoteTicks) == ClimbingCoyoteState::Eligible)) {
334 body->velocity.y = policy.jumpSpeed;
335 body->grounded = false;
336 body->lastOrdinaryJumpPressedTick = jump->pressedTick;
337 body->hasOrdinaryJump = true;
338 } else if (!body->grounded) {
339 body->velocity.y -= policy.gravity * deltaSeconds;
340 } else {
341 body->velocity.y = 0.f;
342 }
343
344 physics::QueryFilter3D filter = policy.queryFilter;
345 filter.ignoredBodyId = linkedBody.value()->getId();
346 const physics::CapsuleMovePolicy3D moverPolicy{
347 body->up.x, body->up.y, body->up.z, body->maxSlopeRadians};
348 const float lowerY = body->feet.y + body->capsuleRadius + body->skin;
349 const float upperY = body->feet.y + body->capsuleHeight - body->capsuleRadius + body->skin;
350 const float snap = body->grounded && !freshJump ? body->groundSnap : 0.f;
351 auto moved = world.moveCapsuleOwned(
352 body->feet.x, lowerY, body->feet.z, body->feet.x, upperY, body->feet.z,
353 body->capsuleRadius, body->velocity.x * deltaSeconds,
354 body->velocity.y * deltaSeconds - snap, body->velocity.z * deltaSeconds,
355 filter, moverPolicy);
356 if (!moved) return eve::Result<std::size_t>::failure(moved.status());
357 physics::CapsuleMove3D movement = std::move(moved).takeValue();
358 std::uint32_t queryCount = 1;
359 std::uint32_t moverIterations = static_cast<std::uint32_t>(movement.iterations);
360 const float desiredX = body->velocity.x * deltaSeconds;
361 const float desiredZ = body->velocity.z * deltaSeconds;
362 const float desiredHorizontal = std::sqrt(desiredX * desiredX + desiredZ * desiredZ);
363 const float directHorizontal = std::sqrt(movement.deltaX * movement.deltaX +
364 movement.deltaZ * movement.deltaZ);
365 if (body->grounded && !freshJump && body->stepHeight > 0.f &&
366 desiredHorizontal > 1e-6f && directHorizontal + body->skin < desiredHorizontal) {
367 auto raised = world.moveCapsuleOwned(
368 body->feet.x, lowerY, body->feet.z, body->feet.x, upperY, body->feet.z,
369 body->capsuleRadius, 0.f, body->stepHeight, 0.f, filter, moverPolicy);
370 if (!raised) return eve::Result<std::size_t>::failure(raised.status());
371 ++queryCount;
372 const physics::CapsuleMove3D rise = std::move(raised).takeValue();
373 moverIterations += static_cast<std::uint32_t>(rise.iterations);
374 if (rise.deltaY + body->skin >= body->stepHeight) {
375 auto crossed = world.moveCapsuleOwned(
376 body->feet.x, lowerY + rise.deltaY, body->feet.z,
377 body->feet.x, upperY + rise.deltaY, body->feet.z,
378 body->capsuleRadius, desiredX, 0.f, desiredZ, filter, moverPolicy);
379 if (!crossed) return eve::Result<std::size_t>::failure(crossed.status());
380 ++queryCount;
381 const physics::CapsuleMove3D across = std::move(crossed).takeValue();
382 moverIterations += static_cast<std::uint32_t>(across.iterations);
383 const float steppedHorizontal = std::sqrt(across.deltaX * across.deltaX +
384 across.deltaZ * across.deltaZ);
385 auto lowered = world.moveCapsuleOwned(
386 body->feet.x + across.deltaX, lowerY + rise.deltaY,
387 body->feet.z + across.deltaZ, body->feet.x + across.deltaX,
388 upperY + rise.deltaY, body->feet.z + across.deltaZ,
389 body->capsuleRadius, 0.f,
390 -(rise.deltaY + body->groundSnap + body->skin), 0.f, filter, moverPolicy);
391 if (!lowered) return eve::Result<std::size_t>::failure(lowered.status());
392 ++queryCount;
393 const physics::CapsuleMove3D down = std::move(lowered).takeValue();
394 moverIterations += static_cast<std::uint32_t>(down.iterations);
395 if (down.grounded && steppedHorizontal > directHorizontal + body->skin) {
396 movement.constrained = rise.constrained || across.constrained || down.constrained;
397 movement.grounded = true;
398 movement.deltaX = across.deltaX;
399 movement.deltaY = rise.deltaY + down.deltaY;
400 movement.deltaZ = across.deltaZ;
401 movement.normalX = down.normalX;
402 movement.normalY = down.normalY;
403 movement.normalZ = down.normalZ;
404 movement.planeCount = rise.planeCount + across.planeCount + down.planeCount;
405 movement.iterations = rise.iterations + across.iterations + down.iterations;
406 }
407 }
408 }
409 body->feet = {body->feet.x + movement.deltaX, body->feet.y + movement.deltaY,
410 body->feet.z + movement.deltaZ};
411 body->grounded = movement.grounded && body->velocity.y <= 0.f;
412 body->mode = body->grounded ? ClimbingMovementMode::Grounded
414 body->velocity.x = movement.deltaX / deltaSeconds;
415 body->velocity.z = movement.deltaZ / deltaSeconds;
416 body->velocity.y = body->grounded ? 0.f : movement.deltaY / deltaSeconds;
417 if (body->grounded) {
418 body->groundNormal = {movement.normalX, movement.normalY, movement.normalZ};
419 body->lastStableFeet = body->feet;
420 body->lastGroundedTick = step.tick;
421 }
422 if (moveLength > 1e-6f)
423 body->forward = {intent->move.x / moveLength, 0.f, intent->move.z / moveLength};
424 linkedBody.value()->setPosition(body->feet.x,
425 body->feet.y + body->capsuleHeight * 0.5f,
426 body->feet.z);
427 const auto ordinaryElapsed = std::chrono::steady_clock::now() - ordinaryStart;
428 runtime->recordOrdinaryTick(
429 step.tick, queryCount, moverIterations,
430 static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(
431 ordinaryElapsed)
432 .count()));
433 ++processed;
434 continue;
435 }
436 const Vec3 previous = body->feet;
437 auto advanced = runtime->advance(world, step, motion);
438 if (!advanced) return eve::Result<std::size_t>::failure(advanced.status());
439 state->lastAdvance = std::move(advanced).takeValue();
440 state->lastAdvanceTick = step.tick;
441 body->feet = state->lastAdvance.feet;
442 const float inverseDelta = static_cast<float>(1.0 / step.delta.seconds());
443 body->velocity = {(body->feet.x - previous.x) * inverseDelta,
444 (body->feet.y - previous.y) * inverseDelta,
445 (body->feet.z - previous.z) * inverseDelta};
446 if (state->lastAdvance.hasTerminalVelocity)
447 body->velocity = state->lastAdvance.terminalVelocity;
448 body->grounded = state->lastAdvance.grounded;
449 body->mode = detail::activeClimbingPhase(state->lastAdvance.phase)
453 if (body->grounded) {
454 body->lastStableFeet = body->feet;
455 body->lastGroundedTick = step.tick;
456 }
457 linkedBody.value()->setPosition(body->feet.x, body->feet.y + body->capsuleHeight * 0.5f,
458 body->feet.z);
459 ++processed;
460 }
462 return eve::Result<std::size_t>::success(processed);
463 }
464};
465
468public:
473 template <class EntityRoot>
476 std::size_t processed = 0;
477 auto view = ecs::View<EntityRoot, ClimbingState, ClimbingPoseProjection, ClimbingEventBatch>();
478 for (auto it = view.begin(); it != view.end(); ++it) {
479 auto [state, pose, events] = *it;
480 auto runtime = Climbing::resolve(state->runtime);
481 if (!runtime.isBound())
484 "climbing runtime link is stale",
485 "state.runtime", {}, "climbing.ecs"));
486 pose->executionId = state->lastAdvance.executionId;
487 pose->leftHandAnchor = state->lastAdvance.leftHandAnchor;
488 pose->rightHandAnchor = state->lastAdvance.rightHandAnchor;
489 pose->leftHandWeight = state->lastAdvance.leftHandWeight;
490 pose->rightHandWeight = state->lastAdvance.rightHandWeight;
491 pose->leftFootWeight = state->lastAdvance.leftFootWeight;
492 pose->rightFootWeight = state->lastAdvance.rightFootWeight;
493 pose->pelvisWeight = state->lastAdvance.pelvisWeight;
494 pose->compactCollision = state->lastAdvance.compactCollisionActive;
495 auto drained = runtime->drainEvents();
496 if (!drained) return eve::Result<std::size_t>::failure(drained.status());
497 auto replaced = events->replace(drained.value(), tick);
498 if (!replaced) return eve::Result<std::size_t>::failure(replaced.status());
499 ++processed;
500 }
502 return eve::Result<std::size_t>::success(processed);
503 }
504};
505
506} // namespace eve::climbing
eve::EntitySpatialPose pose
float phase
Definition CaveMesh.cpp:58
Tick-addressed command buffering for deterministic climbing input.
Deterministic climbing/parkour planning and capsule-constrained execution.
#define EVENGINE_API_DOMAINS
Definition Export.h:110
HexVec3 across
std::array< float, 3 > scale
graphics::Canvas * previous
std::unique_ptr< gpgpu::GpuBuffer > buffer
Definition OnnxGpgpu.cpp:26
World3D * world
glm::mat4 view
std::string filter
Battle::Events events
SimulationTick tick
float step
Definition TreeMesh.cpp:314
std::string body
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
Fixed-capacity owning candidate projection written once per PrePhysics probe phase.
Definition ClimbingECS.h:66
void clear() noexcept
Clear derived candidates without affecting runtime state.
Definition ClimbingECS.h:81
eve::SimulationTick tick() const noexcept
Tick that produced the current projection.
Definition ClimbingECS.h:83
std::span< const ClimbingCandidate > values() const noexcept
Return an immutable synchronous view invalidated by the next replace/clear.
Definition ClimbingECS.h:77
Fixed-capacity owning candidate set used by the production probe hot path.
Definition Climbing.h:329
PrePhysics selection/commit consumer over caller-selected domain entities.
static eve::Result< std::size_t > step(physics::World3D &world, ClimbingCommand command, eve::SimulationTick tick)
Consume at most one buffered command and commit one execution per visible entity.
Bounded owning post-simulation event batch safe to dispatch after the ECS View closes.
eve::SimulationTick tick() const noexcept
Simulation tick at which the runtime queue was drained.
std::span< const ClimbingEvent > values() const noexcept
Immutable synchronous event view.
static ClimbingCoyoteState coyoteWindowState(eve::SimulationTick currentTick, eve::SimulationTick lastGroundedTick, std::uint64_t coyoteTicks) noexcept
Evaluate a grounded grace window without reading wall clock.
static std::optional< BufferedClimbingCommand > peek(const ClimbingIntent &intent, ClimbingCommand command, eve::SimulationTick currentTick) noexcept
Observe the oldest eligible command without consuming or pruning intent.
Physics-phase authoritative capsule motion consumer.
static eve::Result< std::size_t > step(physics::World3D &world, const eve::SimulationStep &step, const ClimbingMotionInput &motion={})
Advance active executions and publish the corrected feet transform to Body and linked Physics body.
PostPhysics derived pose and owning event-drain producer.
static eve::Result< std::size_t > step(eve::SimulationTick tick)
Project pose constraints and drain runtime events without invoking external consumers inside the View...
PrePhysics candidate producer over a caller-selected existing domain root.
static eve::Result< std::size_t > step(physics::World3D &world, eve::SimulationTick tick)
Probe every entity in View<EntityRoot, Body, Intent, State, Links, CandidateBuffer>.
Owner-thread climbing planner and executor for one character.
Definition Climbing.h:653
static eve::Result< ClimbingStart > tryStart(ClimbingRuntime &runtime, physics::World3D &world, const ClimbingPose &pose, ClimbingIntent &intent, ClimbingCommand command, eve::SimulationTick tick, eve::SimulationTick lastGroundedTick)
Start the best candidate and consume its input edge exactly once.
static eve::script::Borrowed< ClimbingRuntime > resolve(ClimbingRuntimeHandleRef reference) noexcept
Resolves a live runtime as a non-owning observation.
static constexpr StrongUint64 zero() noexcept
Returns the zero value for this strong type.
Box3D rigid-body world. Script coordinates are meters (Box3D native), unlike 2D World which uses pixe...
Definition World3D.h:63
bool activeClimbingPhase(ClimbingPhase phase) noexcept
Active climbing phase.
std::span< const ClimbingSystemContract > climbingSystemContracts() noexcept
Return immutable process-lifetime contracts for the four climbing ECS phases.
ClimbingPhase
Execution lifecycle visible to gameplay and animation adapters.
Definition Climbing.h:470
ClimbingMovementMode
Movement mode used by action source-mode hard validation.
Definition Climbing.h:67
ClimbingCommand
Edge and held commands understood by the climbing input phase.
One deterministic fixed-step emitted by SimulationClock.
Definition Time.h:158
One owning edge command with deterministic lifetime and consumption evidence.
Owning output of one execution tick.
Definition Climbing.h:553
Hot authoritative transient motor component; replicated by value for prediction.
Definition ClimbingECS.h:31
eve::SimulationTick lastOrdinaryJumpPressedTick
Definition ClimbingECS.h:47
ClimbingMovementMode mode
Definition ClimbingECS.h:45
eve::SimulationTick lastGroundedTick
Definition ClimbingECS.h:46
Hot authoritative intent component; contains no device or callback pointers.
Per-tick authored animation motion supplied before climbing warp and collision.
Definition Climbing.h:540
Derived PostPhysics pose projection; gameplay validity never depends on these weights.
Input pose used for one deterministic candidate probe.
Definition Climbing.h:275
Hot authoritative execution component holding the module-owned runtime identity.
Definition ClimbingECS.h:96
ClimbingRuntimeHandleRef runtime
Definition ClimbingECS.h:97
ClimbingAdvance lastAdvance
Definition ClimbingECS.h:98
Static review/tooling description of one concrete climbing ECS phase.
Small owning world-space vector used at climbing module boundaries.
bool started
Definition Graphics.cpp:183