| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- // 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
|