Files
voxelforge/core/include/vf/Buoyancy.h
T

129 lines
4.7 KiB
C++

#pragma once
#include <cstdint>
#include "Collide.h"
#include "Damage.h"
#include "Effect.h"
#include "Fluid.h"
#include "Math.h"
#include "World.h"
namespace vf {
// Being in a fluid, measured by volume rather than by a flag.
//
// The current pushing a body around is read straight out of Fluid.h's FluidSim -
// the levels its cellular automaton already settles - instead of a second flow
// model that would disagree with the water the world actually renders. Breath
// and drowning are turned into DamageEvents for Damage.h; nothing here computes
// health on its own.
bool isFluidBlock(Block block);
struct FluidSample {
Block fluid = Block::Air;
float submergedFraction = 0.0f; // 0 dry, 1 fully under
float submergedVolume = 0.0f;
float surfaceY = 0.0f; // top of the highest fluid column touching the box
float eyeDepth = 0.0f; // metres of fluid above the eyes, 0 when they are clear
bool feetInFluid = false;
bool eyesInFluid = false;
bool inFluid() const { return fluid != Block::Air && submergedFraction > 0.0f; }
bool inWater() const { return fluid == Block::Water; }
bool inLava() const { return fluid == Block::Lava; }
};
// Every cell is a full column of fluid: the cheap sample, for worlds where water
// was stamped into blocks rather than simulated.
FluidSample sampleFluid(const World& world, const SweptBox& box, float eyeHeight);
// Column heights come from the automaton's levels, so a one-eighth flow edge
// only wets the soles of a body standing in it.
FluidSample sampleFluid(const World& world, const FluidSim& fluid, const SweptBox& box,
float eyeHeight);
struct BuoyancyParams {
float gravity = 0.08f;
// Below 1 the body still sinks under its own weight; above 1 it is pushed
// back out. Lava is the denser of the two and holds a body up.
float waterDensity = 0.80f;
float lavaDensity = 1.40f;
float waterLinearDrag = 0.80f;
float lavaLinearDrag = 0.50f;
float quadraticDrag = 0.03f;
float swimUpAccel = 0.039f;
float sinkAccel = 0.030f;
float currentStrength = 0.020f;
float maxRiseSpeed = 0.36f;
int maxAirTicks = 300;
int drownIntervalTicks = 20;
float drownDamage = 2.0f;
int lavaBurnIntervalTicks = 10;
float lavaBurnDamage = 4.0f;
};
float fluidDensity(Block fluid, const BuoyancyParams& params);
float fluidLinearDrag(Block fluid, const BuoyancyParams& params);
// Air left in the lungs and the two countdowns that turn an empty bar into
// periodic damage.
struct BreathState {
int airTicks = 300;
int drownTimer = 0;
int burnTimer = 0;
};
// Reads the settled fluid levels as a flow direction: water runs from the full
// cells toward the shallow ones and pours into any gap under them.
class CurrentField {
public:
CurrentField() = default;
CurrentField(const World& world, const FluidSim& fluid);
void attach(const World& world, const FluidSim& fluid);
bool valid() const { return world_ != nullptr && fluid_ != nullptr; }
const FluidSim* fluid() const { return fluid_; }
// Unit flow at one cell, zero where there is no fluid or nowhere to run.
Vec3 flowAt(int gx, int gy, int gz) const;
// Mean flow over every fluid cell the box occupies.
Vec3 flowAtBox(const SweptBox& box) const;
private:
const World* world_ = nullptr;
const FluidSim* fluid_ = nullptr;
};
struct BuoyancyResult {
FluidSample sample;
Vec3 velocity;
Vec3 buoyantAcceleration;
Vec3 dragDelta;
Vec3 currentPush;
bool submerged = false;
bool eyesUnder = false;
bool drowning = false;
bool tookFluidDamage = false;
DamageEvent fluidHit;
};
// Buoyancy, drag and deliberate swimming applied to a velocity that has already
// had gravity subtracted for this tick. Partial submersion scales all three, so
// half a body in water gets half the lift.
Vec3 buoyantVelocity(const FluidSample& sample, const Vec3& velocity,
const BuoyancyParams& params, bool swimUp, bool sinkDown);
// Advances the breath bar. Returns true when the tick owes damage, filling
// `hit` with a Damage.h event of the right type.
bool updateBreath(const FluidSample& sample, BreathState& breath, const BuoyancyParams& params,
const EffectSet& effects, const Vec3& position, DamageEvent& hit);
// One fluid tick for a body: sample, lift, drag, current and breath.
BuoyancyResult stepBuoyancy(const World& world, const SweptBox& box, const Vec3& velocity,
const CurrentField& current, const BuoyancyParams& params,
const EffectSet& effects, BreathState& breath, float eyeHeight,
bool swimUp, bool sinkDown);
} // namespace vf