37std::string trimCopy(
const std::string &
s) {
39 while (
b <
s.size() && std::isspace(
static_cast<unsigned char>(
s[
b]))) ++
b;
41 while (e >
b && std::isspace(
static_cast<unsigned char>(
s[e - 1]))) --e;
42 return s.substr(
b, e -
b);
45bool parseBoolish(
const std::string &
v,
bool &out) {
48 for (
char c :
v)
t.push_back(static_cast<char>(
std::tolower(static_cast<unsigned char>(
c))));
49 if (
t ==
"1" ||
t ==
"true" ||
t ==
"on" ||
t ==
"yes") {
53 if (
t ==
"0" ||
t ==
"false" ||
t ==
"off" ||
t ==
"no") {
63 equipmentLayers_.emplace(
"body", EquipmentLayer{
"", 0, {}});
64 equipmentLayers_.emplace(
"underwear", EquipmentLayer{
"body", 100, {}});
65 equipmentLayers_.emplace(
"shirt", EquipmentLayer{
"underwear", 200, {}});
66 equipmentLayers_.emplace(
"outerwear", EquipmentLayer{
"shirt", 300, {}});
67 equipmentLayers_.emplace(
"cape", EquipmentLayer{
"outerwear", 400, {}});
68 equipmentLayers_.emplace(
"weapon", EquipmentLayer{
"body", 500, {}});
69 if (
auto *mod = ModuleManager::getInstance<Avatar>(
"Avatar"))
70 mod->registerInstance(
this);
76 if (released_)
return;
81 for (
auto &[hookId, hook] : destroyHooks_) {
87 destroyHooks_.clear();
89 animPlayer_ =
nullptr;
90 animLayerMixer_ =
nullptr;
91 animStateMachine_ =
nullptr;
92 animConstraintStack_.reset();
93 footIKSolver_.reset();
94 dynamicBoneSolver_ =
nullptr;
96 animSkeleton_ =
nullptr;
97 attachmentPoseMatrices_.clear();
100 hideEquipmentVisuals();
101 equipment_ =
nullptr;
102 equipmentVisuals_.clear();
103 skinnedParts_.clear();
104 projectedEquipment_.clear();
105 skinnedPositions_.clear();
106 if (sceneAnchor2d_) {
107 ecs::DestroyEntity(sceneAnchor2d_);
108 sceneAnchor2d_ =
nullptr;
110 sceneLinked_ =
false;
111 if (
auto *mod = ModuleManager::getInstance<Avatar>(
"Avatar"))
112 mod->unregisterInstance(
this);
121 return static_cast<size_t>(-1);
122 const size_t id = nextDestroyHookId_++;
123 destroyHooks_.emplace_back(
id, std::move(hook));
128 for (
auto it = destroyHooks_.begin(); it != destroyHooks_.end(); ++it) {
129 if (it->first ==
id) {
130 destroyHooks_.erase(it);
152 equipment_ = equipment;
153 equipmentSignature_.clear();
158 const bool hadBinding = equipment_ !=
nullptr || !projectedEquipment_.empty();
159 equipment_ =
nullptr;
160 equipmentSignature_.clear();
161 projectedEquipment_.clear();
162 hideEquipmentVisuals();
167 const std::string&
itemId,
const std::string& equipmentSlot,
const std::string& layerName,
169 if (kind_ !=
"image" ||
itemId.empty() || equipmentSlot.empty() || layerName.empty())
171 for (
auto& visual : equipmentVisuals_) {
172 if (visual.itemId !=
itemId || visual.equipmentSlot != equipmentSlot)
continue;
173 if (visual.layerName == layerName && visual.texture == texture && visual.zIndex == zIndex)
175 visual.layerName = layerName;
176 visual.texture = texture;
177 visual.zIndex = zIndex;
178 equipmentSignature_.clear();
181 EquipmentVisual visual;
183 visual.equipmentSlot = equipmentSlot;
184 visual.layerName = layerName;
185 visual.texture = texture;
186 visual.wearLayer = equipmentSlot;
187 visual.zIndex = zIndex;
188 equipmentVisuals_.push_back(std::move(visual));
189 equipmentSignature_.clear();
194 const std::string&
itemId,
const std::string& equipmentSlot,
197 if (kind_ !=
"vroid" ||
itemId.empty() || equipmentSlot.empty() || !renderable)
199 for (
auto& visual : equipmentVisuals_) {
200 if (visual.itemId !=
itemId || visual.equipmentSlot != equipmentSlot)
continue;
201 if (visual.renderable == renderable && visual.boneName == boneSemanticOrName &&
202 visual.ox ==
ox && visual.oy ==
oy && visual.oz == oz)
204 visual.renderable = renderable;
205 visual.boneName = boneSemanticOrName;
209 equipmentSignature_.clear();
212 EquipmentVisual visual;
214 visual.equipmentSlot = equipmentSlot;
215 visual.renderable = renderable;
216 visual.boneName = boneSemanticOrName;
217 visual.wearLayer = equipmentSlot;
221 equipmentVisuals_.push_back(std::move(visual));
223 equipmentSignature_.clear();
228 const std::string&
itemId,
const std::string& equipmentSlot,
int partIndex,
231 if (kind_ !=
"vroid" ||
itemId.empty() || equipmentSlot.empty() || partName.empty() ||
232 !
mesh || !skin || partIndex < 0 ||
235 if (!renderable3d_) renderable3d_ = graphics::Renderable3D::create();
236 for (
const auto& part : skinnedParts_) {
239 for (
const auto& visual : equipmentVisuals_) {
240 if (visual.partIndex == partIndex && visual.equipmentSlot != equipmentSlot)
243 for (
auto& visual : equipmentVisuals_) {
244 if (visual.itemId !=
itemId || visual.equipmentSlot != equipmentSlot)
continue;
245 if (visual.partIndex == partIndex && visual.layerName == partName &&
246 visual.skinnedMesh ==
mesh && visual.skinnedMaterial ==
material &&
247 visual.skinnedSkin == skin)
249 if (renderable3d_ && visual.partIndex >= 0) {
250 renderable3d_->
setPart(visual.partIndex, visual.layerName,
nullptr,
nullptr);
253 if (visual.renderable) visual.renderable->setVisible(
false);
254 if (!visual.boneName.empty())
detachAttachment(
"equipment:" + visual.equipmentSlot);
255 visual.partIndex = partIndex;
256 visual.layerName = partName;
257 visual.skinnedMesh =
mesh;
259 visual.skinnedSkin = skin;
260 visual.renderable =
nullptr;
261 visual.boneName.clear();
262 visual.cpuPositions.clear();
263 equipmentSignature_.clear();
266 EquipmentVisual visual;
268 visual.equipmentSlot = equipmentSlot;
269 visual.layerName = partName;
270 visual.wearLayer = equipmentSlot;
271 visual.partIndex = partIndex;
272 visual.skinnedMesh =
mesh;
274 visual.skinnedSkin = skin;
275 equipmentVisuals_.push_back(std::move(visual));
276 equipmentSignature_.clear();
281 const std::string&
parent) {
284 auto found = equipmentLayers_.find(
name);
291 equipmentSignature_.clear();
296 const std::string& innerLayer) {
297 const auto outer = equipmentLayers_.find(outerLayer);
298 const auto inner = equipmentLayers_.find(innerLayer);
299 if (outer == equipmentLayers_.end() || inner == equipmentLayers_.end() ||
300 outerLayer == innerLayer || outer->second.order <= inner->second.order)
302 auto& occludes = outer->second.occludes;
303 if (std::find(occludes.begin(), occludes.end(), innerLayer) != occludes.end())
305 occludes.push_back(innerLayer);
306 equipmentSignature_.clear();
311 const std::string&
itemId,
const std::string& equipmentSlot,
const std::string& wearLayer,
312 int withinLayerOrder) {
314 for (
auto& visual : equipmentVisuals_) {
315 if (visual.itemId !=
itemId || visual.equipmentSlot != equipmentSlot)
continue;
316 if (visual.wearLayer == wearLayer && visual.withinLayerOrder == withinLayerOrder)
318 visual.wearLayer = wearLayer;
319 visual.withinLayerOrder = withinLayerOrder;
320 equipmentSignature_.clear();
326int AvatarInstance::equipmentLayerOrder(
const std::string&
name)
const {
327 const auto found = equipmentLayers_.find(
name);
328 return found == equipmentLayers_.end() ? 0 :
found->second.order;
331void AvatarInstance::hideEquipmentVisuals() {
332 for (
auto& visual : equipmentVisuals_) {
334 if (kind_ ==
"image" && !visual.layerName.empty()) {
337 if (visual.renderable) visual.renderable->setVisible(
false);
338 if (visual.partIndex >= 0 && renderable3d_)
339 renderable3d_->
setPart(visual.partIndex, visual.layerName,
nullptr,
nullptr);
340 if (visual.partIndex >= 0 && renderable3d_)
342 if (!visual.boneName.empty())
detachAttachment(
"equipment:" + visual.equipmentSlot);
348 std::string signature;
349 std::unordered_map<std::string, std::string> equipped;
353 signature += slot +
"=" + item +
"#" + std::to_string(equipment_->
getSlotInstanceId(slot)) +
";";
354 if (!item.empty()) equipped.emplace(slot, item);
358 hideEquipmentVisuals();
359 projectedEquipment_.clear();
360 equipmentRenderStack_.clear();
361 std::vector<std::string> occludedLayers;
362 for (
const auto& visual : equipmentVisuals_) {
363 const auto active = equipped.find(visual.equipmentSlot);
364 if (
active == equipped.end() ||
active->second != visual.itemId)
continue;
365 const auto layer = equipmentLayers_.find(visual.wearLayer);
366 if (
layer == equipmentLayers_.end())
continue;
367 occludedLayers.insert(occludedLayers.end(),
layer->second.occludes.begin(),
368 layer->second.occludes.end());
370 for (
auto& visual : equipmentVisuals_) {
371 const auto active = equipped.find(visual.equipmentSlot);
372 if (
active == equipped.end() ||
active->second != visual.itemId)
continue;
373 if (std::find(occludedLayers.begin(), occludedLayers.end(), visual.wearLayer) !=
374 occludedLayers.end())
376 const int renderOrder = equipmentLayerOrder(visual.wearLayer) * 1000 +
377 visual.withinLayerOrder;
378 if (kind_ ==
"image" && !visual.layerName.empty()) {
379 if (!
hasLayer(visual.layerName))
addLayer(visual.layerName, visual.texture, visual.zIndex);
381 setLayerZ(visual.layerName, renderOrder + visual.zIndex);
384 if (visual.renderable) {
385 visual.renderable->setVisible(visible_);
386 if (visual.boneName.empty()) {
387 visual.renderable->setPosition(x3_ + visual.ox, y3_ + visual.oy, z3_ + visual.oz);
388 visual.renderable->setRotation(yaw_, pitch_, roll_);
389 visual.renderable->setScale(sx3_, sy3_, sz3_);
391 attachToBone(
"equipment:" + visual.equipmentSlot, visual.boneName, visual.renderable,
392 visual.ox, visual.oy, visual.oz);
395 if (visual.partIndex >= 0 && visual.skinnedMesh && renderable3d_) {
396 renderable3d_->
setPart(visual.partIndex, visual.layerName, visual.skinnedMesh,
397 visual.skinnedMaterial);
400 projectedEquipment_[visual.equipmentSlot] = visual.itemId;
401 equipmentRenderStack_.push_back({visual.itemId, visual.wearLayer, renderOrder});
403 std::stable_sort(equipmentRenderStack_.begin(), equipmentRenderStack_.end(),
404 [](
const EquipmentRenderEntry&
a,
const EquipmentRenderEntry&
b) {
405 if (a.order != b.order) return a.order < b.order;
406 return a.itemId < b.itemId;
408 equipmentSignature_ = std::move(signature);
413 const auto found = projectedEquipment_.find(equipmentSlot);
414 return found == projectedEquipment_.end() ? std::string{} :
found->second;
418 return static_cast<int>(equipmentRenderStack_.size());
422 return index < 0 || index >=
static_cast<int>(equipmentRenderStack_.size())
424 : equipmentRenderStack_[
static_cast<size_t>(
index)].
itemId;
428 return index < 0 || index >=
static_cast<int>(equipmentRenderStack_.size())
430 : equipmentRenderStack_[
static_cast<size_t>(
index)].wearLayer;
435 for (
const auto& e : vrm_->document.expressions)
setParameter(e.name, e.name ==
name ? 1.f : 0.f);
440 if (kind_ ==
"image") {
442 }
else if (kind_ ==
"live2d") {
445 }
else if (kind_ ==
"vroid") {
446 if (expressionDefs_.count(
name)) {
448 }
else if (!
name.empty()) {
453 ensureParameter(mn, 0.f);
456 ensureParameter(
name, 1.f);
457 syncMorphWeightsToMesh();
464 if (kind_ ==
"live2d") {
468 if (kind_ ==
"vroid") {
469 auto it = motions_.find(
name);
470 if (animPlayer_ && it != motions_.end()) {
471 if (animPlayer_->
isPlaying() && motionBlendTime_ > 0.f)
472 animPlayer_->
crossFade(it->second, motionBlendTime_);
474 animPlayer_->
play(it->second);
475 hasPreviousRoot_ =
false;
483 if (
name.empty())
return;
485 if (kind_ ==
"live2d") {
488 }
else if (kind_ ==
"vroid") {
490 }
else if (kind_ ==
"image") {
492 if (Layer *L = findLayer(
name)) {
494 if (
a < 0.f)
a = 0.f;
495 if (
a > 1.f)
a = 1.f;
496 L->entity->sprite()->a =
a;
497 L->visible =
a > 0.001f;
498 L->entity->sprite()->visible = visible_ && L->visible;
505 auto it = parameters_.find(
name);
506 return it == parameters_.end() ? 0.f : it->second;
510 if (kind_ ==
"live2d" && live2d_) {
513 return parameters_.find(
name) != parameters_.end();
519 if (
index < 0 ||
size_t(
index) >= parameterOrder_.size())
return {};
520 return parameterOrder_[size_t(
index)];
526 updateExpressionTransition(dt);
527 if (kind_ ==
"live2d") {
529 if (live2d_) live2d_->
update(dt);
531 if (kind_ ==
"vroid") updateSkeletalAnimation(dt);
535 if (kind_ !=
"vroid" || vrm_)
return false;
536 animPlayer_ = player;
537 animStateMachine_ =
nullptr;
538 animLayerMixer_ =
nullptr;
539 animSkeleton_ = player ? player->
getSkeleton() :
nullptr;
540 attachmentPoseMatrices_.clear();
541 hasPreviousRoot_ =
false;
542 if (footIKSolver_) footIKSolver_->setSkeleton(animSkeleton_);
543 if (animConstraintStack_) animConstraintStack_->setSkeleton(animSkeleton_);
544 if (dynamicBoneSolver_) dynamicBoneSolver_->setSkeleton(animSkeleton_);
545 return player !=
nullptr;
549 if (kind_ !=
"vroid" || vrm_)
return false;
550 animStateMachine_ = machine;
551 animPlayer_ =
nullptr;
552 animLayerMixer_ =
nullptr;
553 animSkeleton_ = machine ? machine->
getSkeleton() :
nullptr;
554 attachmentPoseMatrices_.clear();
555 hasPreviousRoot_ =
false;
556 if (footIKSolver_) footIKSolver_->setSkeleton(animSkeleton_);
557 if (animConstraintStack_) animConstraintStack_->setSkeleton(animSkeleton_);
558 if (dynamicBoneSolver_) dynamicBoneSolver_->setSkeleton(animSkeleton_);
559 return machine !=
nullptr;
563 if (kind_ !=
"vroid" || vrm_)
return false;
564 animLayerMixer_ = mixer;
565 animStateMachine_ =
nullptr;
567 animSkeleton_ = mixer ? mixer->
getSkeleton() :
nullptr;
568 attachmentPoseMatrices_.clear();
569 hasPreviousRoot_ =
false;
570 if (footIKSolver_) footIKSolver_->setSkeleton(animSkeleton_);
571 if (animConstraintStack_) animConstraintStack_->setSkeleton(animSkeleton_);
572 if (dynamicBoneSolver_) dynamicBoneSolver_->setSkeleton(animSkeleton_);
573 return mixer !=
nullptr;
577 if (kind_ !=
"vroid" || !stack) {
578 animConstraintStack_.reset();
581 animConstraintStack_ = std::make_unique<animation::AnimConstraintStack>(*stack);
582 animConstraintStack_->setSkeleton(animSkeleton_);
586 if (kind_ !=
"vroid" || !solver) {
587 footIKSolver_.reset();
590 footIKSolver_ = std::make_unique<animation::FootIKSolver>(*solver);
591 footIKSolver_->setSkeleton(animSkeleton_);
595 if (kind_ !=
"vroid") {
596 dynamicBoneSolver_.reset();
600 dynamicBoneSolver_.reset();
603 dynamicBoneSolver_ = std::make_unique<animation::DynamicBoneSolver>(*solver);
604 if (dynamicBoneSolver_) dynamicBoneSolver_->setSkeleton(animSkeleton_);
608 if (kind_ !=
"vroid" || vrm_)
return false;
610 skinnedPositions_.clear();
611 return skin !=
nullptr;
617 if (kind_ !=
"vroid" || partIndex < 0 ||
621 for (
const auto& visual : equipmentVisuals_) {
624 for (
auto& part : skinnedParts_) {
625 if (part.partIndex != partIndex)
continue;
626 if (part.partName == partName && part.mesh ==
mesh && part.material ==
material &&
629 part.partName = partName;
633 part.cpuPositions.clear();
634 if (!renderable3d_) renderable3d_ = graphics::Renderable3D::create();
639 part.partIndex = partIndex;
640 part.partName = partName;
644 skinnedParts_.push_back(std::move(part));
645 if (!renderable3d_) renderable3d_ = graphics::Renderable3D::create();
651 const auto found = std::find_if(skinnedParts_.begin(), skinnedParts_.end(),
652 [&](
const SkinnedPart& part) {
653 return part.partIndex == partIndex;
656 skinnedParts_.erase(
found);
657 if (renderable3d_) renderable3d_->
setPart(partIndex, {},
nullptr,
nullptr);
662 for (
const auto& part : skinnedParts_) {
663 if (part.partIndex == partIndex)
return part.updateMode;
665 for (
const auto& visual : equipmentVisuals_) {
666 if (visual.partIndex == partIndex)
return visual.updateMode;
672 if (kind_ !=
"vroid" ||
name.empty() || !
clip)
return false;
681 hasPreviousRoot_ =
false;
686AvatarInstance::Layer *AvatarInstance::findLayer(
const std::string &
name) {
687 for (
auto &L : layers_)
692const AvatarInstance::Layer *AvatarInstance::findLayer(
const std::string &
name)
const {
693 for (
const auto &L : layers_)
699 if (kind_ !=
"image" ||
name.empty() || findLayer(
name))
return false;
703 L.entity = graphics::Renderable2D::create();
704 auto sp = L.entity->sprite();
705 sp->texture = texture;
708 sp->receiveLight =
false;
711 sp->height = float(texture->
getHeight());
713 layers_.push_back(L);
718 Layer *L = findLayer(
name);
719 if (!L)
return false;
720 auto sp = L->entity->sprite();
721 sp->texture = texture;
724 sp->height = float(texture->
getHeight());
730 Layer *L = findLayer(
name);
731 if (!L)
return false;
733 L->entity->sprite()->visible = visible_ &&
visible;
738 Layer *L = findLayer(
name);
739 if (!L)
return false;
746 Layer *L = findLayer(
name);
747 if (!L)
return false;
748 L->entity->setColor(
r,
g,
b,
a);
753 Layer *L = findLayer(
name);
754 if (!L)
return false;
760 Layer *L = findLayer(
name);
761 if (!L)
return false;
763 L->entity->setSize(
w,
h);
770 if (
index < 0 ||
size_t(
index) >= layers_.size())
return {};
771 return layers_[size_t(
index)].name;
777 Layer* L = findLayer(
name);
778 return L ? L->entity :
nullptr;
782 if ((kind_ !=
"image" && kind_ !=
"vroid") ||
name.empty())
return false;
783 expressionDefs_[
name] = spec;
787bool AvatarInstance::applyExpressionSpec(
const std::string &spec) {
788 if (spec.empty())
return true;
789 std::stringstream ss(spec);
791 while (std::getline(ss, part,
';')) {
792 part = trimCopy(part);
793 if (part.empty())
continue;
794 const auto eq = part.find(
'=');
795 if (eq == std::string::npos)
continue;
796 const std::string
key = trimCopy(part.substr(0, eq));
797 const std::string val = trimCopy(part.substr(eq + 1));
798 if (
key.empty())
continue;
800 if (kind_ ==
"vroid") {
802 if (parseBoolish(val, flag)) {
806 const float f = std::strtof(val.c_str(), &
end);
807 if (
end &&
end != val.c_str())
815 Layer *L = findLayer(
key);
822 if (parseBoolish(val, flag)) {
824 L->entity->sprite()->visible = visible_ && flag;
828 L->entity->sprite()->visible = visible_;
830 setParameter(
key +
".variant",
float(std::hash<std::string>{}(val) & 0xffff));
838 for (
const auto& e : vrm_->document.expressions)
839 if (e.name ==
name) {
844 auto it = expressionDefs_.find(
name);
845 if (it == expressionDefs_.end())
return false;
846 expressionBlendFrom_.clear();
847 expressionBlendTo_.clear();
848 expressionBlendDuration_ = 0.f;
850 const bool ok = applyExpressionSpec(it->second);
851 if (kind_ ==
"vroid") syncMorphWeightsToMesh();
856 const auto definition = expressionDefs_.find(
name);
857 if (definition == expressionDefs_.end())
return false;
860 std::unordered_map<std::string, float> targets;
861 if (kind_ ==
"vroid" && boundMesh_ && boundMesh_->
hasMorphData()) {
864 std::stringstream stream(definition->second);
866 while (std::getline(stream, part,
';')) {
867 part = trimCopy(part);
868 const auto eq = part.find(
'=');
869 if (eq == std::string::npos)
continue;
870 const std::string
key = trimCopy(part.substr(0, eq));
871 const std::string
value = trimCopy(part.substr(eq + 1));
872 if (
key.empty())
continue;
874 if (parseBoolish(
value, flag)) {
875 targets[
key] = flag ? 1.f : 0.f;
879 const float numeric = std::strtof(
value.c_str(), &
end);
881 targets[
key] = numeric;
885 expressionBlendFrom_.clear();
886 expressionBlendTo_ = std::move(targets);
887 for (
const auto& [
key,
target] : expressionBlendTo_) {
889 if (kind_ ==
"image") {
890 const Layer*
layer = findLayer(
key);
897 expressionBlendElapsed_ = 0.f;
898 expressionBlendDuration_ =
duration;
902void AvatarInstance::updateExpressionTransition(
float dt) {
903 if (expressionBlendDuration_ <= 0.f || expressionBlendTo_.empty())
return;
904 expressionBlendElapsed_ += std::max(0.f, dt);
905 const float t = std::clamp(expressionBlendElapsed_ / expressionBlendDuration_, 0.f, 1.f);
906 for (
const auto& [
key,
target] : expressionBlendTo_) {
907 const auto start = expressionBlendFrom_.find(
key);
912 expressionBlendFrom_.clear();
913 expressionBlendTo_.clear();
914 expressionBlendDuration_ = 0.f;
918void AvatarInstance::syncImageLayers() {
919 float parentX = 0.f, parentY = 0.f, parentRot = 0.f, parentSx = 1.f, parentSy = 1.f;
920 bool parentVisible =
true;
921 if (sceneLinked_ && sceneAnchor2d_) {
922 auto anchorTf = sceneAnchor2d_->transform();
923 parentX = anchorTf->x;
924 parentY = anchorTf->y;
925 parentRot = anchorTf->rot;
926 parentSx = anchorTf->sx;
927 parentSy = anchorTf->sy;
928 parentVisible = sceneAnchor2d_->sprite()->visible;
930 const float parentRadians = parentRot * 3.14159265358979323846f / 180.f;
931 const float c = std::cos(parentRadians);
932 const float s = std::sin(parentRadians);
933 for (Layer &L : layers_) {
934 if (!L.entity) L.entity = graphics::Renderable2D::create();
935 auto tf = L.entity->transform();
936 auto sp = L.entity->sprite();
937 const float localX = (x_ + L.ox * sx_) * parentSx;
938 const float localY = (y_ + L.oy * sy_) * parentSy;
939 tf->x = parentX +
c * localX -
s * localY;
940 tf->y = parentY +
s * localX +
c * localY;
941 tf->rot = tf->rot - L.appliedParentRotation + parentRot;
942 L.appliedParentRotation = parentRot;
943 tf->sx = sx_ * parentSx;
944 tf->sy = sy_ * parentSy;
945 sp->layer = layer_ + L.zIndex;
946 sp->visible = parentVisible && visible_ && L.visible;
950void AvatarInstance::destroyLayers() {
951 for (Layer &L : layers_) {
953 ecs::DestroyEntity(L.entity);
963 if (kind_ !=
"live2d")
return false;
965 if (!live2d_)
return false;
970 if (live2d_)
return live2d_->
getName();
980 if (kind_ !=
"live2d" || !visible_)
return;
Keyframed skeletal animation clip (local TRS tracks per bone). Script type: AnimClip.
Ordered main-thread post-process stack with aim, limb IK and foot planting.
Combines a borrowed base pose source with ordered override/additive layers.
AnimSkeleton * getSkeleton() const override
Return the mixer skeleton. @ownership Borrowed @lifetime Valid while the owning animation object rema...
AnimPlayer * getBasePlayer() const
Return the base when it is an AnimPlayer; otherwise null. @ownership Borrowed @lifetime Valid while t...
Single-clip (or cross-fading) 3D animation player. Script type: AnimPlayer.
AnimSkeleton * getSkeleton() const override
Borrowed pointer accessor. @ownership Borrowed @lifetime Valid while the owning animation object rema...
void crossFade(AnimClip *clip, float blendSeconds)
Cross-fade to clip over blendSeconds (keeps sampling previous until done).
bool isPlaying() const
True when playing.
void play(AnimClip *clip)
Play.
CPU linear-blend skinning binding for one mesh against an AnimSkeleton.
Clip-driven animation state machine with float/bool/trigger parameters and cross-fade transitions....
void setTrigger(const std::string &name)
Sets the trigger.
AnimSkeleton * getSkeleton() const override
Borrowed pointer accessor. @ownership Borrowed @lifetime Valid while the owning animation object rema...
World-space spring-chain solver that writes results as local bone rotations.
void setScale(float sx, float sy)
Sets the scale.
void setExpression(const std::string &name)
Sets the expression.
bool bindAnimStateMachine(animation::AnimStateMachine *machine)
Bind an animation state machine that the avatar advances each update.
bool setLayerColor(const std::string &name, float r, float g, float b, float a)
Sets the layer color.
void setApplyRootMotion(bool enabled)
Apply root-bone X/Z deltas to the Avatar transform while animating.
bool setLayerOffset(const std::string &name, float ox, float oy)
Sets the layer offset.
EquipmentVisualChange addEquipmentLayerOcclusion(const std::string &outerLayer, const std::string &innerLayer)
Record that an equipped outer layer fully hides an inner layer.
void setLayer(int layer)
Sets the layer.
EquipmentVisualChange bindSkinnedPart(int partIndex, const std::string &partName, graphics::Mesh *mesh, graphics::Material *material, animation::AnimSkin *skin)
Bind an always-visible skinned mesh/material part to the shared Avatar pose.
EquipmentVisualChange unbindSkinnedPart(int partIndex)
Remove a shared-pose skinned part and clear its material slot.
float getParameter(const std::string &name) const
Returns the parameter.
bool registerMotion(const std::string &name, animation::AnimClip *clip)
Register a motion name for setMotion(); the clip is not owned.
bool bindAnimPlayer(animation::AnimPlayer *player)
Bind a clip player that the avatar advances and skins each update.
bool detachAttachment(const std::string &name)
Remove an attachment without destroying its Renderable3D.
~AvatarInstance()
Avatar instance.
bool hasLayer(const std::string &name) const
True when layer.
std::function< void(AvatarInstance *)> DestroyHook
Callback fired exactly once, when the instance is destroyed.
bool attachToBone(const std::string &name, const std::string &boneSemanticOrName, graphics::Renderable3D *renderable, float ox=0.f, float oy=0.f, float oz=0.f)
Attach a non-owned Renderable3D to a mapped humanoid semantic or bone name.
bool applyExpression(const std::string &name)
Applies expression.
EquipmentVisualChange bindEquipment(inventory::EquipmentSet *equipment)
Bind the authoritative equipment set whose visual projection this Avatar rebuilds.
std::string getParameterName(int index) const
Returns the parameter name.
graphics::Renderable2D * getLayerRenderable(const std::string &name)
Return the native Renderable2D owned by a named image layer.
std::string getEquipmentRenderStackItem(int index) const
Active item id at a render-stack index, or empty text.
void removeDestroyHook(size_t id)
Remove a previously registered destruction hook.
bool bindAnimLayerMixer(animation::AnimLayerMixer *mixer)
Bind an override/additive layer mixer that the avatar advances each update.
EquipmentVisualChange unbindEquipment()
Remove the equipment binding and hide all registered equipment visuals.
EquipmentVisualChange setEquipmentVisualLayer(const std::string &itemId, const std::string &equipmentSlot, const std::string &wearLayer, int withinLayerOrder=0)
Assign an item visual to a wear layer and a stable order inside that layer.
void setVisible(bool visible)
Sets the visible.
void update(float dt)
Updates .
int getEquipmentRenderStackCount() const
Number of active visuals in canonical back-to-front render order.
EquipmentVisualChange syncEquipmentAppearance()
Rebuild visuals from the current authoritative slots when their signature changed.
void setAnimConstraintStack(animation::AnimConstraintStack *stack)
Copy an IK/aim stack into Avatar-owned storage; null disables it.
int getLayerCount() const
Returns the layer count.
bool bindAnimSkin(animation::AnimSkin *skin)
Bind CPU skin data used to deform the avatar mesh from the active pose.
void setDynamicBoneSolver(animation::DynamicBoneSolver *solver)
Copy dynamic-bone configuration into Avatar-owned storage.
EquipmentVisualChange defineEquipmentVisual3D(const std::string &itemId, const std::string &equipmentSlot, graphics::Renderable3D *renderable, const std::string &boneSemanticOrName, float ox=0.f, float oy=0.f, float oz=0.f)
Map an item to a borrowed 3D renderable, optionally following a humanoid semantic/bone.
EquipmentVisualChange defineEquipmentVisual2D(const std::string &itemId, const std::string &equipmentSlot, const std::string &layerName, graphics::Texture *texture, int zIndex)
Map an item to a 2D Avatar layer. Texture is borrowed and must remain valid while registered.
size_t addDestroyHook(DestroyHook hook)
Register a destruction hook; returns an id usable with removeDestroyHook().
void setParameter(const std::string &name, float value)
Sets the parameter.
void collectLive2DDrawItems(std::vector< graphics::DrawItem2D > &out)
Append this instance's Live2D drawables to a shared draw queue.
bool transitionExpression(const std::string &name, float duration)
Blend numeric/bool expression channels over duration seconds.
std::string getLayerName(int index) const
Returns the layer name.
std::string getEquipmentVisualItem(const std::string &equipmentSlot) const
Return the currently projected item for a slot, or empty text when none is shown.
bool hasLive2DBackend() const
True when live 2 d backend.
std::string getEquipmentRenderStackLayer(int index) const
Wear-layer name at a render-stack index, or empty text.
void setPosition(float x, float y)
Sets the position.
void setMotion(const std::string &name)
Sets the motion.
AvatarInstance(std::string kind)
Avatar instance.
EquipmentVisualChange defineEquipmentLayer(const std::string &name, int order, const std::string &parent={})
Define a named back-to-front wear layer and its optional semantic parent.
bool loadLive2DModel(const std::string &path)
Loads live 2 d model.
bool setLayerZ(const std::string &name, int zIndex)
Sets the layer z.
int getParameterCount() const
Returns the parameter count.
void setMotionBlendTime(float seconds)
Set the cross-fade duration used when switching registered motions.
bool setLayerTexture(const std::string &name, graphics::Texture *texture)
Sets the layer texture.
bool addLayer(const std::string &name, graphics::Texture *texture, int zIndex)
Adds layer.
EquipmentVisualChange defineEquipmentSkinnedVisual3D(const std::string &itemId, const std::string &equipmentSlot, int partIndex, const std::string &partName, graphics::Mesh *mesh, graphics::Material *material, animation::AnimSkin *skin)
Map wearable equipment to a skinned mesh/material part sharing the Avatar pose.
std::string getLive2DBackendName() const
Returns the live 2 d backend name.
bool setLayerSize(const std::string &name, float w, float h)
Sets the layer size.
bool setLayerVisible(const std::string &name, bool visible)
Sets the layer visible.
bool hasParameter(const std::string &name) const
True when parameter.
void setFootIKSolver(animation::FootIKSolver *solver)
Copy paired-foot IK configuration into Avatar-owned storage; null disables it.
SkinnedPartUpdateMode getSkinnedPartUpdateMode(int partIndex) const
Return the latest GPU/CPU/unavailable update mode for a material slot.
bool defineExpression(const std::string &name, const std::string &spec)
Spec: "eyes=1;mouth=smile;blush=0" (semicolon-separated name=value).
static bool hasLive2DBackend()
True when the active backend is a real rendering runtime, not the null stub.
static std::string getLive2DBackendName()
Backend name currently in effect ("null" when using the built-in stub).
static ILive2DBackend * createLive2DBackend()
Always returns a backend: custom factory, else NullLive2DBackend.
virtual void setParameter(const std::string &name, float value)=0
Sets the parameter.
virtual void update(float dt)=0
Updates .
virtual void collectDrawItems(std::vector< graphics::DrawItem2D > &)
Optional: push drawables into the 2D queue. Default no-op.
virtual std::string getName() const =0
Returns the name.
virtual bool setMotion(const std::string &name)=0
Sets the motion.
virtual bool isRuntimeAvailable() const
True only for a backend that can render a real Live2D model.
virtual float getParameter(const std::string &name) const =0
Returns the parameter.
virtual bool loadModel(const std::string &path)=0
Loads model.
virtual bool setExpression(const std::string &name)=0
Sets the expression.
Packages shading method + surface parameters into one attachable asset.
GPU mesh handle (+ optional CPU morph targets).
bool hasMorphData() const
True when morph data.
bool setMorphWeight(const std::string &name, float weight)
Sets the morph weight.
bool hasMorph(const std::string &name) const
True when morph.
int getMorphCount() const
Returns the morph count.
std::string getMorphName(int index) const
Returns the morph name.
Default renderable entity for declarative 2D sprites / solid quads.
EVENGINE_API_BACKENDS public API.
void setPartSortPriority(int index, int priority)
Override transparent sorting for one part without mutating its shared Material.
void setPart(int index, const std::string &name, Mesh *mesh, Material *material)
Bind a named mesh+material part (e.g. Assimp submesh / body region). index 0..kMaxParts-1....
void clearPartSortPriority(int index)
Clear a part's per-instance sort override and use its Material priority.
void setVisible(bool visible)
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
bool hasDeferredFilePixels() const
True when deferred file pixels.
int getWidth() const
Returns the width.
int getHeight() const
Returns the height.
EVENGINE_API_FOUNDATION public API.
int getSlotInstanceId(const std::string &slotName) const
Returns the slot instance id.
std::string getSlotName(int index) const
Returns the slot name.
std::string getSlotItemId(const std::string &slotName) const
Returns the slot item id.
int getSlotCount() const
Returns the slot count.
EquipmentVisualChange
Outcome of changing or rebuilding the equipment appearance projection.
SkinnedPartUpdateMode
Observable backend selected for a shared-pose skinned part.
static constexpr int kMaxParts