3#include <glm/gtc/matrix_transform.hpp>
4#include <glm/gtc/quaternion.hpp>
5#include <glm/gtx/quaternion.hpp>
13glm::vec3 vec(
const std::array<float, 3>&
v) {
return {
v[0],
v[1],
v[2]}; }
14glm::mat4 matrix(
const animation::AnimPose&
pose,
int bone) {
19glm::quat quaternion(
const animation::TransformTRS&
t) {
return {
t.qw,
t.qx,
t.qy,
t.qz}; }
20glm::vec3
unit(glm::vec3
v, glm::vec3 fallback = {0, 1, 0}) {
21 float n = glm::length(
v);
22 return n > 1e-7f ?
v /
n : fallback;
24void rotate(animation::AnimPose&
pose,
int bone, glm::quat
q) {
25 q = glm::normalize(
q);
28float mapped(
float value,
const VrmLookAt::Range&
r) {
29 return std::min(std::abs(
value) / std::max(
r.input, .0001f), 1.f) *
r.output;
35 glm::quat delta = glm::angleAxis(angles[0], glm::vec3(0, 1, 0)) *
36 glm::angleAxis(angles[1], glm::vec3(1, 0, 0)) * glm::angleAxis(angles[2], glm::vec3(0, 0, 1));
43 float suppression = 0;
46 if (expression.binary)
value =
value > .5f ? 1.f : 0.f;
47 if (expression.overrideLookAt ==
"block" &&
value > 0)
49 else if (expression.overrideLookAt ==
"blend")
52 weight *= 1 - std::clamp(suppression, 0.f, 1.f);
58 const auto&
h =
pose.world(head);
59 const glm::quat rest = quaternion(bind.
world(head));
60 const glm::quat orientation = quaternion(
h) * glm::inverse(rest);
62 glm::vec3
local = glm::inverse(orientation) * (glm::vec3(glm::inverse(modelWorld) * glm::vec4(
target, 1)) -
origin);
66 const float vertical = mapped(
pitch,
pitch >= 0 ? look.down : look.up) *
weight;
67 if (look.expression) {
68 gazeWeights_[
yaw >= 0 ?
"lookLeft" :
"lookRight"] = mapped(
yaw, look.outer) *
weight;
69 gazeWeights_[
pitch >= 0 ?
"lookDown" :
"lookUp"] = vertical;
72 for (
auto semantic : {
"leftEye",
"rightEye"}) {
76 const bool outer = (
yaw >= 0) == (std::string_view(
semantic) ==
"leftEye");
77 const float horizontal = mapped(
yaw, outer ? look.outer : look.inner) *
weight;
78 glm::quat delta = glm::angleAxis(glm::radians(std::copysign(horizontal,
yaw)), glm::vec3(0, 1, 0)) *
79 glm::angleAxis(glm::radians(std::copysign(vertical,
pitch)), glm::vec3(1, 0, 0));
81 const glm::quat eyeRest = quaternion(bind.
world(
bone));
88 if (!std::isfinite(dt) || dt <= 0)
return;
90 const bool reset = dt > .25f;
91 dt = std::min(dt, .05f);
95 const glm::mat4 inverseCenter = glm::inverse(
center);
96 for (
size_t j = 0; j + 1 < spring.joints.size(); ++j) {
97 const auto& joint = spring.joints[j];
100 const glm::vec3
origin = glm::vec3(boneWorld[3]);
101 const glm::vec3 restTail = glm::vec3(
world * matrix(
pose, tailBone)[3]);
102 const glm::vec3 axis =
unit(restTail -
origin);
104 if (
length < 1e-7f)
continue;
105 auto& particle = particles_[
s][j];
106 if (!particle.initialized || reset) {
107 particle.current = particle.previous = glm::vec3(inverseCenter * glm::vec4(restTail, 1));
108 particle.initialized =
true;
110 const glm::vec3
current = glm::vec3(
center * glm::vec4(particle.current, 1));
111 const glm::vec3
previous = glm::vec3(
center * glm::vec4(particle.previous, 1));
113 vec(joint.gravity) * (dt * joint.gravityPower);
115 for (
int ci : spring.colliders) {
118 glm::vec3 closest = glm::vec3(cm * glm::vec4(vec(
c.offset), 1));
120 glm::vec3 segment = glm::vec3(cm * glm::vec4(vec(
c.tail), 1)) - closest;
121 float sq = glm::dot(segment, segment);
122 if (sq > 1e-12f) closest += segment * std::clamp(glm::dot(next - closest, segment) / sq, 0.f, 1.f);
124 float radius =
c.radius + joint.radius;
125 glm::vec3 delta = next - closest;
131 particle.previous = particle.current;
132 particle.current = glm::vec3(inverseCenter * glm::vec4(next, 1));
133 const glm::vec3 localAxis =
unit(glm::vec3(glm::inverse(boneWorld) * glm::vec4(restTail, 1)));
134 const glm::vec3 localTarget =
unit(glm::vec3(glm::inverse(boneWorld) * glm::vec4(next, 1)), localAxis);
135 rotate(
pose,
bone, quaternion(
pose.local(
bone)) * glm::rotation(localAxis, localTarget));
eve::EntitySpatialPose pose
std::array< double, 10 > q
graphics::Canvas * previous
Evaluated local (and optional world) pose for an AnimSkeleton. Script type: AnimPose.
const TransformTRS & world(int boneIndex) const
World.
void computeWorld(const AnimSkeleton *skeleton)
Compute world transforms from local pose + skeleton hierarchy. World values readable via getWorld* af...
Unified avatar instance. Kind is a string: "image" | "live2d" | "vroid". Script-facing API avoids ove...
float getParameter(const std::string &name) const
Returns the parameter.
std::vector< int > nodeBones
std::unique_ptr< animation::AnimSkeleton > skeleton
void gaze(AvatarInstance &avatar, animation::AnimPose &pose, const glm::mat4 &world, bool enabled, glm::vec3 target, float weight)
Gaze.
std::map< int, std::array< float, 3 > > rotations
std::vector< VrmSpring > springs
std::map< std::string, int > humanoid
std::vector< VrmCollider > colliders
std::vector< VrmExpression > expressions
std::array< float, 3 > offset