feat: add sweepBoxAgainstWorld and boxOverlapsWorld to Collide
This commit is contained in:
1 parent
500fcaffd3
commit
d7cc7d9172
1 file changed
+191
@@ -154,3 +154,194 @@ SweepHit sweepBoxAgainstBox(const SweptBox& mover, const Vec3& motion, const Swe
|
|||||||
SweptBox expanded{obstacle.min - moverSize, obstacle.max};
|
SweptBox expanded{obstacle.min - moverSize, obstacle.max};
|
||||||
Vec3 point = mover.min;
|
Vec3 point = mover.min;
|
||||||
|
|
||||||
|
float enter = -std::numeric_limits<float>::infinity();
|
||||||
|
float exit = std::numeric_limits<float>::infinity();
|
||||||
|
int enterAxis = -1;
|
||||||
|
float enterSign = 0.0f;
|
||||||
|
|
||||||
|
for (int a = 0; a < 3; ++a) {
|
||||||
|
float origin = component(point, a);
|
||||||
|
float travel = component(motion, a);
|
||||||
|
float lo = component(expanded.min, a);
|
||||||
|
float hi = component(expanded.max, a);
|
||||||
|
if (std::fabs(travel) < parallelMotion) {
|
||||||
|
if (origin <= lo || origin >= hi) return hit;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
float toLow = (lo - origin) / travel;
|
||||||
|
float toHigh = (hi - origin) / travel;
|
||||||
|
float axisEnter = toLow;
|
||||||
|
float axisExit = toHigh;
|
||||||
|
float sign = -1.0f;
|
||||||
|
if (toLow > toHigh) {
|
||||||
|
axisEnter = toHigh;
|
||||||
|
axisExit = toLow;
|
||||||
|
sign = 1.0f;
|
||||||
|
}
|
||||||
|
if (axisEnter > enter) {
|
||||||
|
enter = axisEnter;
|
||||||
|
enterAxis = a;
|
||||||
|
enterSign = sign;
|
||||||
|
}
|
||||||
|
if (axisExit < exit) exit = axisExit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enterAxis < 0) return hit;
|
||||||
|
if (enter > exit || enter >= 1.0f || exit <= 0.0f || enter < 0.0f) return hit;
|
||||||
|
|
||||||
|
hit.hit = true;
|
||||||
|
hit.time = enter;
|
||||||
|
hit.axis = axisFromIndex(enterAxis);
|
||||||
|
setComponent(hit.normal, enterAxis, enterSign);
|
||||||
|
return hit;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BroadPhase::gather(const World& world, const SweptBox& box, const Vec3& motion) {
|
||||||
|
cells_.clear();
|
||||||
|
envelope_ = inflatedBox(motionEnvelope(box, motion), gatherPadding);
|
||||||
|
|
||||||
|
int x0 = floorToCell(envelope_.min.x);
|
||||||
|
int x1 = floorToCell(envelope_.max.x);
|
||||||
|
int y0 = floorToCell(envelope_.min.y) - shapeOverhangCells;
|
||||||
|
int y1 = floorToCell(envelope_.max.y);
|
||||||
|
int z0 = floorToCell(envelope_.min.z);
|
||||||
|
int z1 = floorToCell(envelope_.max.z);
|
||||||
|
|
||||||
|
for (int x = x0; x <= x1; ++x) {
|
||||||
|
for (int y = y0; y <= y1; ++y) {
|
||||||
|
for (int z = z0; z <= z1; ++z) {
|
||||||
|
Vec3i cell{x, y, z};
|
||||||
|
Block block = world.blockAt(x, y, z);
|
||||||
|
CollisionShape shape = collisionShapeOf(block);
|
||||||
|
if (!shapeBlocksMovement(shape)) continue;
|
||||||
|
ShapeBoxes solids = shapeBoxesAt(shape, cell);
|
||||||
|
bool relevant = false;
|
||||||
|
for (int i = 0; i < solids.count; ++i) {
|
||||||
|
if (boxesOverlap(envelope_, solids.boxes[i])) relevant = true;
|
||||||
|
}
|
||||||
|
if (!relevant) continue;
|
||||||
|
cells_.push_back(BroadPhaseCell{cell, block, shape, solids});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BroadPhase::gatherStatic(const World& world, const SweptBox& box) {
|
||||||
|
gather(world, box, Vec3{0.0f, 0.0f, 0.0f});
|
||||||
|
}
|
||||||
|
|
||||||
|
SweepHit sweepBoxAgainstWorld(const World& world, const SweptBox& box, const Vec3& motion,
|
||||||
|
BroadPhase& broadPhase) {
|
||||||
|
broadPhase.gather(world, box, motion);
|
||||||
|
SweepHit best;
|
||||||
|
if (motionIsIdle(motion)) return best;
|
||||||
|
|
||||||
|
for (const BroadPhaseCell& entry : broadPhase.cells()) {
|
||||||
|
for (int i = 0; i < entry.solids.count; ++i) {
|
||||||
|
SweepHit candidate = sweepBoxAgainstBox(box, motion, entry.solids.boxes[i]);
|
||||||
|
if (!candidate.hit) continue;
|
||||||
|
if (best.hit && candidate.time >= best.time) continue;
|
||||||
|
candidate.cell = entry.cell;
|
||||||
|
candidate.block = entry.block;
|
||||||
|
best = candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool boxOverlapsWorld(const World& world, const SweptBox& box) {
|
||||||
|
BroadPhase broadPhase;
|
||||||
|
broadPhase.gatherStatic(world, box);
|
||||||
|
for (const BroadPhaseCell& entry : broadPhase.cells()) {
|
||||||
|
for (int i = 0; i < entry.solids.count; ++i) {
|
||||||
|
if (boxesOverlap(box, entry.solids.boxes[i])) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool restsOnGround(const World& world, const SweptBox& box, float probeDepth) {
|
||||||
|
SweptBox probe = box;
|
||||||
|
probe.max.y = box.min.y;
|
||||||
|
probe.min.y = box.min.y - probeDepth;
|
||||||
|
// Pull the footprint in so a body flush against a wall does not read the
|
||||||
|
// wall itself as a floor.
|
||||||
|
probe.min.x += contactSkin;
|
||||||
|
probe.min.z += contactSkin;
|
||||||
|
probe.max.x -= contactSkin;
|
||||||
|
probe.max.z -= contactSkin;
|
||||||
|
return boxOverlapsWorld(world, probe);
|
||||||
|
}
|
||||||
|
|
||||||
|
float supportHeightBelow(const World& world, const SweptBox& box, float maxDrop) {
|
||||||
|
BroadPhase broadPhase;
|
||||||
|
Vec3 drop{0.0f, -maxDrop, 0.0f};
|
||||||
|
SweepHit hit = sweepBoxAgainstWorld(world, box, drop, broadPhase);
|
||||||
|
if (!hit.hit) return box.min.y - maxDrop;
|
||||||
|
return box.min.y - maxDrop * hit.time;
|
||||||
|
}
|
||||||
|
|
||||||
|
CollisionResult resolveMove(const World& world, const SweptBox& box, const Vec3& motion,
|
||||||
|
BroadPhase& broadPhase) {
|
||||||
|
CollisionResult out;
|
||||||
|
out.box = box;
|
||||||
|
Vec3 remaining = motion;
|
||||||
|
|
||||||
|
for (int pass = 0; pass < maxSlidePasses; ++pass) {
|
||||||
|
if (motionIsIdle(remaining)) break;
|
||||||
|
out.passes = pass + 1;
|
||||||
|
|
||||||
|
SweepHit hit = sweepBoxAgainstWorld(world, out.box, remaining, broadPhase);
|
||||||
|
if (!hit.hit) {
|
||||||
|
out.box = translatedBox(out.box, remaining);
|
||||||
|
out.appliedMotion += remaining;
|
||||||
|
remaining = Vec3{0.0f, 0.0f, 0.0f};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!out.firstHit.hit) out.firstHit = hit;
|
||||||
|
|
||||||
|
Vec3 step = remaining * hit.time;
|
||||||
|
Vec3 leftover = remaining - step;
|
||||||
|
|
||||||
|
int a = axisIndex(hit.axis);
|
||||||
|
float backedOff = component(step, a) + component(hit.normal, a) * contactSkin;
|
||||||
|
if (backedOff * component(remaining, a) < 0.0f) backedOff = 0.0f;
|
||||||
|
setComponent(step, a, backedOff);
|
||||||
|
|
||||||
|
out.box = translatedBox(out.box, step);
|
||||||
|
out.appliedMotion += step;
|
||||||
|
|
||||||
|
switch (hit.axis) {
|
||||||
|
case ContactAxis::X: out.touchedX = true; break;
|
||||||
|
case ContactAxis::Y:
|
||||||
|
out.touchedY = true;
|
||||||
|
if (hit.normal.y > 0.0f) out.onGround = true;
|
||||||
|
if (hit.normal.y < 0.0f) out.hitCeiling = true;
|
||||||
|
break;
|
||||||
|
case ContactAxis::Z: out.touchedZ = true; break;
|
||||||
|
case ContactAxis::None: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
setComponent(leftover, a, 0.0f);
|
||||||
|
remaining = leftover;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.remainingMotion = remaining;
|
||||||
|
if (!out.onGround && restsOnGround(world, out.box)) out.onGround = true;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
CollisionResult resolveMove(const World& world, const SweptBox& box, const Vec3& motion) {
|
||||||
|
BroadPhase broadPhase;
|
||||||
|
return resolveMove(world, box, motion, broadPhase);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3 clipVelocity(const Vec3& velocity, const CollisionResult& collision) {
|
||||||
|
Vec3 out = velocity;
|
||||||
|
if (collision.touchedX) out.x = 0.0f;
|
||||||
|
if (collision.touchedY) out.y = 0.0f;
|
||||||
|
if (collision.touchedZ) out.z = 0.0f;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace vf
|
||||||
Reference in new issue
Block a user