Files
2026-03-04 00:50:15 -08:00

231 lines
13 KiB
C++

// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Physics/Hair/HairSettings.h>
#include <Jolt/Physics/Collision/ObjectLayer.h>
#include <Jolt/Physics/Collision/Shape/Shape.h>
#include <Jolt/Core/StridedPtr.h>
#include <Jolt/Core/NonCopyable.h>
JPH_NAMESPACE_BEGIN
class PhysicsSystem;
#ifdef JPH_DEBUG_RENDERER
class DebugRenderer;
#endif
class HairShaders;
/// Hair simulation instance
///
/// Note that this system is currently still in development, it is missing important features like:
///
/// - Level of detail
/// - Wind forces
/// - Advection step for the grid velocity field
/// - Support for collision detection against shapes other than ConvexHullShape
/// - The Gradient class is very limited and will be replaced by a texture lookup
/// - Gravity preload factor is not fully functioning yet
/// - It is wasteful of memory (e.g. stores everything both on CPU and GPU)
/// - Only supports a single neutral pose to drive towards
/// - It could use further optimizations
class JPH_EXPORT Hair : public NonCopyable
{
public:
/// Constructor / destructor
Hair(const HairSettings *inSettings, RVec3Arg inPosition, QuatArg inRotation, ObjectLayer inLayer);
~Hair();
/// Initialize
void Init(ComputeSystem *inComputeSystem);
/// Position and rotation of the hair in world space
void SetPosition(RVec3Arg inPosition) { mPosition = inPosition; }
void SetRotation(QuatArg inRotation) { mRotation = inRotation; }
RMat44 GetWorldTransform() const { return RMat44::sRotationTranslation(mRotation, mPosition); }
/// Access to the hair settings object which contains the configuration of the hair
const HairSettings * GetHairSettings() const { return mSettings; }
/// The hair will be initialized in its default pose with zero velocity at the new position and rotation during the next update
void OnTeleported() { mTeleported = true; }
/// Ability to externally provide the scalp vertices buffer. This allows skipping skinning the scalp during the simulation update. You may need to override JPH_SHADER_BIND_SCALP_VERTICES in HairSkinRootsBindings.h to match the format of the provided buffer.
void SetScalpVerticesCB(ComputeBuffer *inBuffer) { mScalpVerticesCB = inBuffer; }
/// Ability to externally provide the scalp triangle indices buffer. This allows skipping skinning the scalp in during the simulation update. You may need to override JPH_SHADER_BIND_SCALP_TRIANGLES in HairSkinRootsBindings.h to match the format of the provided buffer.
void SetScalpTrianglesCB(ComputeBuffer *inBuffer) { mScalpTrianglesCB = inBuffer; }
/// When skipping skinning, this allow specifying a transform that transforms the scalp mesh into head space.
void SetScalpToHead(Mat44Arg inMat) { mScalpToHead = inMat; }
/// Function that converts the render positions buffer to Float3 vertices for debugging purposes. It maps an application defined format to Float3. Third parameter is the number of vertices.
using RenderPositionsToFloat3 = std::function<void(ComputeBuffer *, Float3 *, uint)>;
/// Enable externally set render vertices buffer (with potentially different vertex layout). Note that this also requires replacing the HairCalculateRenderPositions shader.
void OverrideRenderPositionsCB(const RenderPositionsToFloat3 &inRenderPositionsToFloat3) { JPH_ASSERT(mRenderPositionsCB == nullptr, "Must be called before Init"); mRenderPositionsOverridden = true; mRenderPositionsToFloat3 = inRenderPositionsToFloat3; }
/// Allow setting the render vertices buffer externally in case it has special requirements for the calling application. You may need to override JPH_SHADER_BIND_RENDER_POSITIONS in HairCalculateRenderPositionsBindings.h to match the format of the provided buffer.
void SetRenderPositionsCB(ComputeBuffer *inBuffer) { JPH_ASSERT(mRenderPositionsOverridden, "Must call OverrideRenderPositionsCB first"); mRenderPositionsCB = inBuffer; }
/// Step the hair simulation forward in time
/// @param inDeltaTime Time step
/// @param inJointToHair Transform that transforms from joint space to hair local space (as defined by GetWorldTransform)
/// @param inJointMatrices Array of joint matrices in world space, length needs to match HairSettings::mScalpInverseBindPose.size()
/// @param inSystem Physics system used for collision detection
/// @param inShaders Preloaded hair compute shaders
/// @param inComputeSystem Compute system to use
/// @param inComputeQueue Compute queue to use
void Update(float inDeltaTime, Mat44Arg inJointToHair, const Mat44 *inJointMatrices, const PhysicsSystem &inSystem, const HairShaders &inShaders, ComputeSystem *inComputeSystem, ComputeQueue *inComputeQueue);
/// Access to the resulting simulation data
ComputeBuffer * GetScalpVerticesCB() const { return mScalpVerticesCB; } ///< Skinned scalp vertices
ComputeBuffer * GetScalpTrianglesCB() const { return mScalpTrianglesCB; } ///< Skinned scalp triangle indices
ComputeBuffer * GetPositionsCB() const { return mPositionsCB; } ///< Note transposed for better memory access
ComputeBuffer * GetVelocitiesCB() const { return mVelocitiesCB; } ///< Note transposed for better memory access
ComputeBuffer * GetVelocityAndDensityCB() const { return mVelocityAndDensityCB; } ///< Velocity grid
ComputeBuffer * GetRenderPositionsCB() const { return mRenderPositionsCB; } ///< Render positions of the hair strands (see HairSettings::mRenderStrands to see where each strand starts and ends)
/// Read back the GPU state so that the functions below can be used. For debugging purposes only, this is slow!
void ReadBackGPUState(ComputeQueue *inComputeQueue);
/// Lock/unlock the data buffers so that the functions below return valid values.
void LockReadBackBuffers();
void UnlockReadBackBuffers();
/// Access to the resulting simulation data (only valid when ReadBackGPUState has been called and the buffers have been locked)
const Float3 * GetScalpVertices() const { return mScalpVertices; }
const Float3 * GetPositions() const { return mPositions; }
const Quat * GetRotations() const { return mRotations; }
StridedPtr<const Float3> GetVelocities() const { return { (const Float3 *)&mVelocities->mVelocity, sizeof(JPH_HairVelocity) }; }
StridedPtr<const Float3> GetAngularVelocities() const { return { (const Float3 *)&mVelocities->mAngularVelocity, sizeof(JPH_HairVelocity) }; }
const Float4 * GetGridVelocityAndDensity() const { return mVelocityAndDensity; }
const Float3 * GetRenderPositions() const { return mRenderPositions; }
#ifdef JPH_DEBUG_RENDERER
enum class ERenderStrandColor
{
PerRenderStrand,
PerSimulatedStrand,
GravityFactor,
WorldTransformInfluence,
GridVelocityFactor,
GlobalPose,
SkinGlobalPose,
};
struct DrawSettings
{
/// This specifies the range of simulation strands to draw, when drawing render strands we only draw the strands that belong to these simulation strands.
uint mSimulationStrandBegin = 0;
uint mSimulationStrandEnd = UINT_MAX;
bool mDrawRods = true; ///< Draws the simulated rods
bool mDrawUnloadedRods = false; ///< Draw rods in their unloaded pose. This pose is obtained by removing gravity influence from the modeled pose.
bool mDrawVertexVelocity = false; ///< Draws the velocity at each simulated vertex as an arrow
bool mDrawAngularVelocity = false; ///< Draws the angular velocity at each simulated vertex as an arrow
bool mDrawOrientations = false; ///< Draws a coordinate space for each simulated vertex
bool mDrawNeutralDensity = false; ///< Draws grid density of the hair in its neutral pose
bool mDrawGridDensity = false; ///< Draws the current grid density of the hair
bool mDrawGridVelocity = false; ///< Draws the velocity of each grid cell as an arrow
bool mDrawSkinPoints = false; ///< Draws the skinning points on the scalp
bool mDrawRenderStrands = false; ///< Draws the render strands (slow, for debugging purposes!)
bool mDrawInitialGravity = true; ///< Draws the configured initial gravity vector used to calculate the unloaded vertex positions
ERenderStrandColor mRenderStrandColor = ERenderStrandColor::PerSimulatedStrand; ///< Color for each strand
};
/// Debug functionality to draw the hair and its simulation properties
void Draw(const DrawSettings &inSettings, DebugRenderer *inRenderer);
#endif // JPH_DEBUG_RENDERER
protected:
using Gradient = HairSettings::Gradient;
using GradientSampler = HairSettings::GradientSampler;
// Information about a colliding shape. Is always a leaf shape, compound shapes are expanded.
struct LeafShape
{
LeafShape() = default;
LeafShape(Mat44Arg inTransform, Vec3Arg inScale, Vec3Arg inLinearVelocity, Vec3Arg inAngularVelocity, const Shape *inShape) : mTransform(inTransform), mScale(inScale), mLinearVelocity(inLinearVelocity), mAngularVelocity(inAngularVelocity), mShape(inShape) { }
Mat44 mTransform;
Vec3 mScale;
Vec3 mLinearVelocity;
Vec3 mAngularVelocity;
RefConst<Shape> mShape;
};
// Internal context used during a simulation step
struct UpdateContext
{
Mat44 mDeltaTransform; // Transforms positions from the old hair transform to the new
Quat mDeltaTransformQuat; // Rotation part of mDeltaTransform
uint mNumIterations; // Number of iterations to run the solver for
bool mNeedsCollision; // If collision detection should be performed
bool mNeedsGrid; // If the grid should be calculated
bool mGlobalPoseOnly; // If no simulation is needed and only the global pose needs to be applied
bool mHasTransformChanged; // If the world transform has changed
float mDeltaTime; // Delta time for a sub step
float mHalfDeltaTime; // 0.5 * mDeltaTime
float mInvDeltaTimeSq; // 1 / mDeltaTime^2
float mTwoDivDeltaTime; // 2 / mDeltaTime
float mTimeRatio; // Ratio between sub step delta time and default sub step delta time
Vec3 mSubStepGravity; // Gravity to apply in a sub step
Array<LeafShape> mShapes; // List of colliding shapes
};
// Calculate the UpdateContext parameters
void InitializeContext(UpdateContext &outCtx, float inDeltaTime, const PhysicsSystem &inSystem);
RefConst<HairSettings> mSettings; // Shared hair settings, must be kept alive during the lifetime of this hair instance
RVec3 mPrevPosition; // Position at the start of the last time step
RVec3 mPosition; // Current position in world space
Quat mPrevRotation; // Rotation at the start of the last time step
Quat mRotation; // Current rotation in world space
bool mTeleported = true; // If the hair got teleported and should be set to the default pose
ObjectLayer mLayer; // Layer for the hair to collide with
Mat44 mScalpToHead = Mat44::sIdentity(); // When skipping skinning, this allow specifying a transform that transforms the scalp mesh into head space
bool mRenderPositionsOverridden = false; // Indicates that the render positions buffer is provided externally
RenderPositionsToFloat3 mRenderPositionsToFloat3; // Function that transforms the render positions buffer to Float3 vertices for debugging purposes
Ref<ComputeBuffer> mScalpJointMatricesCB;
Ref<ComputeBuffer> mScalpVerticesCB;
Ref<ComputeBuffer> mScalpTrianglesCB;
Ref<ComputeBuffer> mTargetPositionsCB; // Target root positions determined by skinning (where we're interpolating to, eventually written to mPositionsCB)
Ref<ComputeBuffer> mTargetGlobalPoseTransformsCB; // Target global pose transforms determined by skinning (where we're interpolating to, eventually written to mGlobalPoseTransformsCB)
Ref<ComputeBuffer> mGlobalPoseTransformsCB; // Current global pose transforms used for skinning the hairs
Ref<ComputeBuffer> mShapePlanesCB;
Ref<ComputeBuffer> mShapeVerticesCB;
Ref<ComputeBuffer> mShapeIndicesCB;
Ref<ComputeBuffer> mCollisionPlanesCB;
Ref<ComputeBuffer> mCollisionShapesCB;
Ref<ComputeBuffer> mMaterialsCB;
Ref<ComputeBuffer> mPreviousPositionsCB;
Ref<ComputeBuffer> mPositionsCB;
Ref<ComputeBuffer> mVelocitiesCB;
Ref<ComputeBuffer> mVelocityAndDensityCB;
Ref<ComputeBuffer> mConstantsCB;
Array<Ref<ComputeBuffer>> mIterationConstantsCB;
Ref<ComputeBuffer> mRenderPositionsCB;
// Only valid after ReadBackGPUState has been called
Ref<ComputeBuffer> mScalpVerticesReadBackCB;
Ref<ComputeBuffer> mPositionsReadBackCB;
Ref<ComputeBuffer> mVelocitiesReadBackCB;
Ref<ComputeBuffer> mVelocityAndDensityReadBackCB;
Ref<ComputeBuffer> mRenderPositionsReadBackCB;
const Float3 * mScalpVertices = nullptr;
Float3 * mPositions = nullptr;
Quat * mRotations = nullptr;
JPH_HairVelocity * mVelocities = nullptr;
const Float4 * mVelocityAndDensity = nullptr;
const Float3 * mRenderPositions = nullptr;
};
JPH_NAMESPACE_END