Files
EVEngine/test/hair_dynamic_bones.cpp
西风逍遥游andCursor Agent 3c38a633fc feat(graphics,animation): hair cards + DynamicBone hair helpers (#400)
* feat(graphics,animation): solidify hair cards + DynamicBone hair helpers

Improve card-path hair for game characters: Kajiya-Kay wrap/scatter knobs,
procedural card mesh/material/LOD helpers, Material hair surface defaults,
and animation setupHairChain/setupHairHeadCollider convenience APIs.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* fix(animation): avoid copying Result in hair DynamicBone tests

zeroerr REQUIRE on Result<T> tries to copy a move-only type; assert .ok() instead.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* fix(graphics): document hair card pointer API ownership/lifetime

Source-quality api-lifetime rejected undocumented raw-pointer returns in
HairCards.h and the HairWgsl string literal; match WaterfallWgsl contracts.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-09-15 09:11:32 +08:00

45 lines
1.5 KiB
C++

#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
#include "animation/AnimSkeleton.h"
#include "animation/DynamicBoneSolver.h"
#include "animation/HairDynamicBones.h"
using eve::animation::AnimSkeleton;
using eve::animation::DynamicBoneSolver;
TEST_CASE("animation.HairDynamicBones.setupChainsAndHeadCollider") {
AnimSkeleton skeleton;
const int head = skeleton.addBone("head");
const int root = skeleton.addBone("hair_root", head);
const int mid = skeleton.addBone("hair_mid", root);
const int tip = skeleton.addBone("hair_tip", mid);
REQUIRE(tip >= 0);
skeleton.setBindPosition(mid, 0.f, -0.2f, 0.f);
skeleton.setBindPosition(tip, 0.f, -0.2f, 0.f);
DynamicBoneSolver solver(&skeleton);
eve::animation::hair::ChainDesc chain;
chain.rootBone = "hair_root";
chain.tipBone = "hair_tip";
chain.stiffness = 0.12f;
chain.damping = 0.18f;
chain.inertia = 0.8f;
chain.endLength = 0.05f;
chain.selfCollision = true;
auto added = eve::animation::hair::addChain(solver, chain);
REQUIRE(added.ok());
CHECK_EQ(added.value(), 0);
CHECK_EQ(solver.getChainCount(), 1);
auto headCol = eve::animation::hair::addHeadCollider(solver, "head", 0.12f);
REQUIRE(headCol.ok());
CHECK(headCol.value() >= 1);
std::vector<eve::animation::hair::ChainDesc> batch = {chain};
batch[0].rootBone = "missing_root";
auto bad = eve::animation::hair::setupChains(solver, batch, false);
CHECK(!bad.ok());
}