载入中...
搜索中...
未找到
Rope3DCollision.cpp
浏览该文件的文档.
2
3#include "common/Exception.h"
4#include "physics/Body3D.h"
6#include "physics/World3D.h"
7
8#include <algorithm>
9#include <cmath>
10
11namespace eve::physics {
12namespace {
13Rope3D::Vec3 subtract(Rope3D::Vec3 a, Rope3D::Vec3 b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; }
14Rope3D::Vec3 add(Rope3D::Vec3 a, Rope3D::Vec3 b) { return {a.x + b.x, a.y + b.y, a.z + b.z}; }
15Rope3D::Vec3 multiply(Rope3D::Vec3 value, float scale) { return {value.x * scale, value.y * scale, value.z * scale}; }
16float dot(Rope3D::Vec3 a, Rope3D::Vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
17float magnitude(Rope3D::Vec3 value) { return std::sqrt(dot(value, value)); }
18bool finite(Rope3D::Vec3 value) { return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z); }
19} // namespace
20
22 if (!std::isfinite(value) || value < 0.f || value > 1.f)
23 throw Exception("Rope3D: collision friction must be in [0,1]");
24 collisionFriction_ = value;
25}
26
28 if (!std::isfinite(value) || value < 0.f || value > 1.f)
29 throw Exception("Rope3D: collision restitution must be in [0,1]");
30 collisionRestitution_ = value;
31}
32
33eve::Result<RopeColliderId> Rope3D::addSphereCollider(float x, float y, float z, float colliderRadius) {
34 if (!finite({x, y, z}) || !std::isfinite(colliderRadius) || colliderRadius <= 0.f)
36 eve::DiagnosticCode::InvalidArgument, "Sphere position and radius must be finite and radius positive",
37 "physics.rope3d.collider.sphere"));
38 Collider collider;
39 collider.id = RopeColliderId{nextColliderId_++};
40 collider.kind = ColliderKind::Sphere;
41 collider.position = {x, y, z};
42 collider.radius = colliderRadius;
43 colliders_.push_back(collider);
45}
46
47eve::Result<RopeColliderId> Rope3D::addPlaneCollider(float x, float y, float z, float nx, float ny, float nz) {
48 const Vec3 normal{nx, ny, nz};
49 const float normalLength = magnitude(normal);
50 if (!finite({x, y, z}) || !finite(normal) || normalLength <= 1e-6f)
52 eve::DiagnosticCode::InvalidArgument, "Plane position and non-zero normal must be finite",
53 "physics.rope3d.collider.plane"));
54 Collider collider;
55 collider.id = RopeColliderId{nextColliderId_++};
56 collider.kind = ColliderKind::Plane;
57 collider.position = {x, y, z};
58 collider.normal = multiply(normal, 1.f / normalLength);
59 colliders_.push_back(collider);
61}
62
64 if (!id || !finite({x, y, z}))
66 "Collider id and position must be valid",
67 "physics.rope3d.collider.move"));
68 const auto found =
69 std::find_if(colliders_.begin(), colliders_.end(), [&](const Collider& collider) { return collider.id == id; });
70 if (found == colliders_.end())
72 eve::DiagnosticCode::NotFound, "Collider id is stale", "physics.rope3d.collider.move"));
73 if (found->kind != ColliderKind::Sphere)
75 eve::DiagnosticCode::Conflict, "Only sphere colliders can be moved by this operation",
76 "physics.rope3d.collider.move"));
77 if (found->position.x == x && found->position.y == y && found->position.z == z)
79 found->position = {x, y, z};
81}
82
84 if (!id)
86 eve::DiagnosticCode::InvalidArgument, "Collider id must be valid", "physics.rope3d.collider.remove"));
87 const auto found =
88 std::find_if(colliders_.begin(), colliders_.end(), [&](const Collider& collider) { return collider.id == id; });
89 if (found == colliders_.end())
91 eve::DiagnosticCode::NotFound, "Collider id is stale", "physics.rope3d.collider.remove"));
92 colliders_.erase(found);
94}
95
96void Rope3D::solveExternalCollisions() {
97 for (auto& particle : particles_) {
98 if (particle.attached) continue;
99 for (const auto& collider : colliders_) {
100 Vec3 normal{};
101 float penetration = 0.f;
102 if (collider.kind == ColliderKind::Sphere) {
103 const Vec3 delta = subtract(particle.position, collider.position);
104 const float distance = magnitude(delta);
105 const float minimumDistance = radius_ + collider.radius;
106 if (distance >= minimumDistance) continue;
107 normal = distance > 1e-6f ? multiply(delta, 1.f / distance) : Vec3{1.f, 0.f, 0.f};
108 penetration = minimumDistance - distance;
109 } else {
110 normal = collider.normal;
111 const float distance = dot(subtract(particle.position, collider.position), normal);
112 if (distance >= radius_) continue;
113 penetration = radius_ - distance;
114 }
115 particle.position = add(particle.position, multiply(normal, penetration));
116 if (collisionFriction_ > 0.f) {
117 const Vec3 velocity = subtract(particle.position, particle.previous);
118 const Vec3 tangent = subtract(velocity, multiply(normal, dot(velocity, normal)));
119 particle.previous = add(particle.previous, multiply(tangent, collisionFriction_));
120 }
121 }
122 }
123}
124
125void Rope3D::collideBorrowedSurfaces(float dt) {
126 if (dt <= 1e-6f) return;
127 for (auto& particle : particles_) {
128 if (particle.attached) continue;
129 const Vec3 predicted = particle.position;
130 const Vec3 sweep = subtract(predicted, particle.previous);
131
132 Vec3 normal{};
133 Vec3 contactPoint = predicted;
134 Body3D* body = nullptr;
135 bool hit = false;
136
137 if (world_ && world_->isValid()) {
138 if (continuousCollision_ && magnitude(sweep) > 1e-7f &&
139 world_->castSphere(particle.previous.x, particle.previous.y, particle.previous.z, radius_, sweep.x,
140 sweep.y, sweep.z) >= 0) {
141 const float fraction = std::clamp(world_->getShapeCastFraction(), 0.f, 1.f);
142 normal = {world_->getShapeCastNormalX(), world_->getShapeCastNormalY(), world_->getShapeCastNormalZ()};
143 particle.position = add(particle.previous, multiply(sweep, fraction));
144 particle.position = add(particle.position, multiply(normal, 1e-4f));
145 contactPoint = {world_->getShapeCastX(), world_->getShapeCastY(), world_->getShapeCastZ()};
146 body = world_->findBodyById(world_->getShapeCastBodyId());
147 hit = true;
148 } else {
149 ClothContact3D contact;
150 if (world_->pointProbe(predicted.x, predicted.y, predicted.z, radius_, &contact) && contact.hit) {
151 normal = {contact.nx, contact.ny, contact.nz};
152 particle.position = add(predicted, multiply(normal, contact.depth));
153 contactPoint = subtract(particle.position, multiply(normal, radius_));
154 body = contact.body;
155 hit = true;
156 }
157 }
158 }
159
160 if (!hit && sdf_) {
161 if (continuousCollision_ && magnitude(sweep) > 1e-7f &&
162 sdf_->castSphere(particle.previous.x, particle.previous.y, particle.previous.z, radius_, sweep.x,
163 sweep.y, sweep.z)) {
164 normal = {sdf_->getNormalX(), sdf_->getNormalY(), sdf_->getNormalZ()};
165 particle.position = add(particle.previous, multiply(sweep, sdf_->getCastFraction()));
166 particle.position = add(particle.position, multiply(normal, 1e-4f));
167 hit = true;
168 } else if (sdf_->checkSphere(predicted.x, predicted.y, predicted.z, radius_)) {
169 normal = {sdf_->getNormalX(), sdf_->getNormalY(), sdf_->getNormalZ()};
170 particle.position = add(predicted, multiply(normal, sdf_->getPenetrationDepth()));
171 hit = true;
172 }
173 }
174
175 if (!hit || magnitude(normal) <= 1e-6f) continue;
176 normal = multiply(normal, 1.f / magnitude(normal));
177 Vec3 bodyVelocity{};
178 float bodyMass = 0.f;
179 const bool dynamic = body && body->getType() == "dynamic";
180 if (dynamic) {
181 bodyVelocity = {body->getLinearVelocityX(), body->getLinearVelocityY(), body->getLinearVelocityZ()};
182 bodyMass = body->getMass();
183 }
184 Vec3 velocity = multiply(sweep, 1.f / dt);
185 const float relativeNormalSpeed = dot(subtract(velocity, bodyVelocity), normal);
186 if (relativeNormalSpeed < 0.f) {
187 const float reducedMass =
188 bodyMass > 0.f ? particleMass_ * bodyMass / (particleMass_ + bodyMass) : particleMass_;
189 const float impulse =
190 std::min(-(1.f + collisionRestitution_) * relativeNormalSpeed * reducedMass, particleMass_ * 20.f);
191 velocity = add(velocity, multiply(normal, impulse / particleMass_));
192 const Vec3 tangent = subtract(velocity, multiply(normal, dot(velocity, normal)));
193 velocity = subtract(velocity, multiply(tangent, collisionFriction_));
194 particle.previous = subtract(particle.position, multiply(velocity, dt));
195 if (dynamic && bodyMass > 0.f)
196 body->applyLinearImpulseAt(-normal.x * impulse, -normal.y * impulse, -normal.z * impulse,
197 contactPoint.x, contactPoint.y, contactPoint.z);
198 }
199 }
200}
201
202} // namespace eve::physics
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
Vec3 tangent
Definition CaveMesh.cpp:80
float nx
float nz
float ny
std::array< float, 3 > scale
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float distance
Texture * normal
bool finite
bool hit
bool found
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
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
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
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
float getNormalZ() const
Z component of the most recently sampled collision normal.
float getNormalX() const
X component of the most recently sampled collision normal.
bool castSphere(float x, float y, float z, float radius, float deltaX, float deltaY, float deltaZ)
Sweep a sphere through the field and cache the earliest contact.
float getNormalY() const
Y component of the most recently sampled collision normal.
float getCastFraction() const
Fraction in [0,1] of the requested displacement at the last cast hit.
bool checkSphere(float x, float y, float z, float radius)
Test a sphere against the solid (distance <= radius).
float getPenetrationDepth() const
Non-negative overlap depth from the most recent overlap, cast, or move query.
eve::Result< RopeColliderId > addSphereCollider(float x, float y, float z, float radius)
Adds a sphere collider owned by this rope.
void setCollisionRestitution(float restitution)
Sets particle/world normal restitution in [0,1].
eve::Result< RopeColliderChange > removeCollider(RopeColliderId id)
Removes a rope-owned analytic collider.
void setCollisionFriction(float friction)
Sets tangential contact damping in [0,1].
eve::Result< RopeColliderChange > moveSphereCollider(RopeColliderId id, float x, float y, float z)
Moves a previously created sphere without changing its radius.
eve::Result< RopeColliderId > addPlaneCollider(float x, float y, float z, float nx, float ny, float nz)
Adds a one-sided plane collider using a normalized outward normal.
float getShapeCastX() const
World-space contact point X from the last shape cast.
Definition World3D.h:690
int castSphere(float x, float y, float z, float radius, float dx, float dy, float dz)
Sweep a sphere through the world and return the earliest hit body id, or -1.
Definition World3D.cpp:1788
float getShapeCastZ() const
World-space contact point Z from the last shape cast.
Definition World3D.h:694
float getShapeCastNormalZ() const
Contact normal Z from the last shape cast.
Definition World3D.h:700
int getShapeCastBodyId() const
Stable body id from the last shape cast, or -1.
Definition World3D.h:680
float getShapeCastY() const
World-space contact point Y from the last shape cast.
Definition World3D.h:692
bool isValid() const
True while the underlying Box3D world is alive.
Definition World3D.cpp:430
float getShapeCastFraction() const
Translation fraction in [0,1] at the earliest hit.
Definition World3D.h:702
Body3D * findBodyById(int bodyId) const
Resolves a live body by its world-local stable event/query id.
Definition World3D.cpp:926
float getShapeCastNormalX() const
Contact normal X from the last shape cast.
Definition World3D.h:696
float getShapeCastNormalY() const
Contact normal Y from the last shape cast.
Definition World3D.h:698
bool pointProbe(float x, float y, float z, float radius, ClothContact3D *out) const
Probe the deepest non-sensor shape within radius of a meter-space point. Returns false when nothing i...
Definition World3D.cpp:2080
Optional physics backend for vehicle mobility and body attach.
Definition Climbing.h:36
double dot(const Vec2 &a, const Vec2 &b)
Dot.
Definition UrbanTypes.h:38
Compact solver vector exposed only as a value type.
Definition Rope3D.h:41
Stable identifier for a rope-owned analytic collider.
Definition Rope3D.h:19