DynamicBone
Вторичное движение для хвостов, ушей, волос, верёвок и плащей, симулируемое в движке. DynamicBone
ставится на КОРНЕВУЮ кость цепочки: её поддерево становится цепочками частиц, которые качаются,
отстают и успокаиваются под действием гравитации, ветра и движения персонажа, притягиваются обратно к
позе, которую даёт им анимация, не входят в тело благодаря капсулам DynamicBoneCollider и держатся
над полом. Стадия работает после поздней фазы и IK-цепей, до скиннинга —
Animator, активный ragdoll и записи в кости в поздней фазе — её вход. Подходит
любое дерево узлов, не только скиннутая модель (верёвка из простых узлов, с bones, именующим её
суставы).
Обзор
const main = async () => {
const scene = new Scene()
const fox = await Model.load(asset('./fox.glb'))
scene.add(fox)
scene.open()
fox.anim.playLoop('walk')
const tail = fox.bone('tail')!.aspect(DynamicBone, {
stiffness: [0.5, 0.15], // жёсткий у основания, свободный на кончике
damping: 0.1,
gravity: 2, // м/с²
angleLimit: [30, 45], // градусы от анимированного направления
radius: 0.03, // метры, для коллизий
})
for (const ear of ['earend', 'R_earend']) fox.bone(ear)!.aspect(DynamicBone, { stiffness: 0.6, damping: 0.2, angleLimit: 30, radius: 0.01 })
fox.bone('Hips')!.aspect(DynamicBoneCollider, { radius: 0.11, to: 'chest' }) // капсула, в которую хвост не входит
tail.dynamicBone.floor = 0 // и над плоскостью земли y = 0
console.log(`tail chain: ${tail.dynamicBone.chain.map((b) => b.name).join(' > ')}`)
}
main()DynamicBone
bone.aspect(DynamicBone, { … })
bone.dynamicBone // аспект — каждое поле живое (отправляется при изменении)
bone.dynamicBone.chain // Node[] — кости цепочки, корень первым
bone.dynamicBone.particles // Vec3[] — симулированные мировые позиции: кости, затем кончик на каждый лист
bone.dynamicBone.reset() // прыгнуть на анимированную позу в следующем кадре (телепорт, смена кадра камеры)
bone.dynamicBone.guide([{ bone, at, weight }]) // мировые точки, к которым кости тянутся на каждом подшаге; заменяет, [] очищаетПокостные значения (DynamicBoneCurve) принимают одно число для всей цепочки или пару [root, tip],
интерполируемую вниз по ней по глубине — хвост, жёсткий у основания и свободный на кончике.
| Поле | По умолчанию | Значение |
|---|---|---|
stiffness |
0.3 |
0–1: как быстро кость возвращается к анимированной форме, за 1/60 с. 0 = верёвка, 1 = жёстко (точно анимация) |
damping |
0.1 |
0–1: скорость, теряемая за 1/60 с — 0.02 качается как верёвка, 0.1 успокаивается как хвост, 0.3 — мёд |
gravity |
0 |
м/с², вниз. Висящая поза покоя её не чувствует; торчащая поза свисает настолько, насколько позволяет stiffness |
angleLimit |
0 |
Максимальное отклонение от анимированного направления, градусы на кость (0 = свободно) |
radius |
0.02 |
Радиус частицы для коллизий, метры |
mass |
1 |
Относительная масса на кость: [3, 1] заставляет основание хвоста нести его кончик, 1 делит поровну как верёвка |
weight |
1 |
0–1 смешивание от анимации к симуляции. При 0 ничего не симулируется, и цепочка перевзводится на анимированной позе, когда возвращается |
follow |
0 |
0–1 перемещения персонажа, которое цепочка берёт жёстко (0 = полный хлыст при рывке) |
wind |
[0, 0, 0] |
Мировой ветер, м/с² |
floor |
'none' |
'probe' (луч вниз от корня цепочки против физического мира, каждый кадр), высота (плоскость на этом мировом y) или мировая точка (плоскость через неё, floorNormal вверх) |
floorNormal |
[0, 1, 0] |
Нормаль плоскости пола, когда floor — точка |
floorFriction |
0.5 |
Коэффициент Кулона о пол (0 = лёд, 1 = резиновая подошва) |
colliders |
'auto' |
'auto' = каждый DynamicBoneCollider на той же модели; 'humanoid' = они плюс капсулы, сгенерированные на стандартных костях гуманоида (бёдра, позвоночник, голова, конечности — раскладка Ragdoll); 'none'; или узлы, несущие коллайдеры, которые использовать |
bones |
всё поддерево | Только эти имена костей входят в цепочку (корень всегда входит; пропущенная кость забирает с собой своё поддерево) |
depth |
0 |
Предел глубины под корнем (0 = нет) |
pinned, pinGive |
—, 0 |
Кости, которые едут на анимации, пока их поддеревья симулируются — полосы плаща под одним якорем, перечисленные по кольцу; pinGive = метры, на которые коллайдер может сдвинуть закреплённую кость с её места |
link |
0 |
0–1: связи между костями одной глубины соседних столбцов (плащ; соседи в порядке pinned) |
side |
'none' |
Наружная сторона ткани, для коллайдеров с пометкой oneSided: 'auto' = от оси корневой кости, или направление во фрейме корневой кости |
spin, spinInertia |
0, 0 |
ПОВОРОТ тела: выше 0 цепочка живёт во вращающемся фрейме родительской кости, так что демпфированный плащ едет с поворотом; его инерция возвращается как силы фрейма × spin (1 = физично). spinInertia = постоянная времени, секунды, с которой вращение ткани следует за вращением тела (0 = приклеено к повороту) |
rate |
60 |
Частота подшагов, Гц (не больше 4 подшагов на кадр) |
iterations |
4 |
Проходов ограничений на подшаг, 1–8 (длинная верёвка может хотеть 8) |
teleport |
1 |
Прыжок корня длиннее стольких метров за один кадр сбрасывает цепочку вместо хлыста |
guideHold |
0 |
0–1: насколько крепко цели guide() держатся сквозь проходы ограничений |
edges |
true |
Сегменты костей и связи тоже сталкиваются с капсулами, не только частицы |
Дочерняя кость, несущая DynamicBone или DynamicBoneCollider в момент прикрепления цепочки,
остаётся вне её вместе со своим поддеревом (это другая цепочка или тело).
кость цепочки, прочитанная в update(), всё ещё показывает анимированную позу — стадия работает
после поздней фазы. particles держат симуляцию (по состоянию на последний кадр), а updateBefore()
читает финальную позу предыдущего кадра.
DynamicBoneCollider
bone.aspect(DynamicBoneCollider, { radius?, offset?, end?, to?, oneSided? })| Поле | По умолчанию | Значение |
|---|---|---|
radius |
0.1 |
Метры, в мире — масштаб кости его не трогает |
offset |
[0, 0, 0] |
Начало капсулы, в локальном пространстве кости |
end |
— | Конец капсулы, в локальном пространстве кости |
to |
— | Кость (по имени), чьё начало — конец капсулы, вместо end |
oneSided |
false |
Ткань должна быть на стороне side цепочки от него: пока капсула движется прочь от той стороны, она переносит кость, в которую врезалась, на ту сторону, вместо того чтобы тащить её (рука, махнувшая вперёд под накидкой) |
Ни end, ни to = сфера в offset. Коллайдер подхватывается каждым DynamicBone той же модели с
colliders: 'auto' или 'humanoid', либо используется там, где перечислен.
зависит от хоста. Хост без динамических костей (headless-рендерер: lecodes render /
lecodes test без --sim / --desktop) предупреждает один раз и показывает анимацию как есть;
chain и particles там остаются пустыми.
Смотрите также
- Animator — поза, за которой следуют цепочки
- IK и стопы — решаются прямо перед этой стадией
- Model —
model.bone(name)