载入中...
搜索中...
未找到
AvatarInstance.cpp
浏览该文件的文档.
12#include "animation/Tween.h"
13#include "avatar/Avatar.h"
14#include "avatar/VrmRuntime.h"
15#include "common/ECS.h"
16#include "common/Module.h"
17#include "graphics/DrawItem2D.h"
18#include "graphics/Graphics.h"
19#include "graphics/Mesh.h"
22#include "graphics/Texture.h"
23#include "image/ImageData.h"
24#include "inventory/Equipment.h"
25#include "model3d/ModelData.h"
26#include "scene/Scene.h"
27
28#include <algorithm>
29#include <cctype>
30#include <cstdlib>
31#include <functional>
32#include <sstream>
33
34namespace eve::avatar {
35namespace {
36
37std::string trimCopy(const std::string &s) {
38 size_t b = 0;
39 while (b < s.size() && std::isspace(static_cast<unsigned char>(s[b]))) ++b;
40 size_t e = s.size();
41 while (e > b && std::isspace(static_cast<unsigned char>(s[e - 1]))) --e;
42 return s.substr(b, e - b);
43}
44
45bool parseBoolish(const std::string &v, bool &out) {
46 std::string t;
47 t.reserve(v.size());
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") {
50 out = true;
51 return true;
52 }
53 if (t == "0" || t == "false" || t == "off" || t == "no") {
54 out = false;
55 return true;
56 }
57 return false;
58}
59
60} // namespace
61
62AvatarInstance::AvatarInstance(std::string kind) : kind_(std::move(kind)) {
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);
71}
72
74
76 if (released_) return;
77 released_ = true;
78 // Notify owners (e.g. Dialogue) before the object is destroyed so they can
79 // drop dangling raw pointers. Runs exactly once: the destructor re-enters
80 // release() but the guard above short-circuits.
81 for (auto &[hookId, hook] : destroyHooks_) {
82 try {
83 hook(this);
84 } catch (...) {
85 }
86 }
87 destroyHooks_.clear();
88 tween_ = nullptr;
89 animPlayer_ = nullptr;
90 animLayerMixer_ = nullptr;
91 animStateMachine_ = nullptr;
92 animConstraintStack_.reset();
93 footIKSolver_.reset();
94 dynamicBoneSolver_ = nullptr;
95 animSkin_ = nullptr;
96 animSkeleton_ = nullptr;
97 attachmentPoseMatrices_.clear();
98 motions_.clear();
99 attachments_.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;
109 }
110 sceneLinked_ = false;
111 if (auto *mod = ModuleManager::getInstance<Avatar>("Avatar"))
112 mod->unregisterInstance(this);
113 destroyLayers();
114 destroyVroid();
115 delete live2d_;
116 live2d_ = nullptr;
117}
118
120 if (!hook)
121 return static_cast<size_t>(-1);
122 const size_t id = nextDestroyHookId_++;
123 destroyHooks_.emplace_back(id, std::move(hook));
124 return id;
125}
126
128 for (auto it = destroyHooks_.begin(); it != destroyHooks_.end(); ++it) {
129 if (it->first == id) {
130 destroyHooks_.erase(it);
131 return;
132 }
133 }
134}
135
136void AvatarInstance::setPosition(float x, float y) {
137 x_ = x;
138 y_ = y;
139}
140
141void AvatarInstance::setScale(float sx, float sy) {
142 sx_ = sx;
143 sy_ = sy;
144}
145
147
148void AvatarInstance::setLayer(int layer) { layer_ = layer; }
149
151 if (!equipment) return unbindEquipment();
152 equipment_ = equipment;
153 equipmentSignature_.clear();
155}
156
158 const bool hadBinding = equipment_ != nullptr || !projectedEquipment_.empty();
159 equipment_ = nullptr;
160 equipmentSignature_.clear();
161 projectedEquipment_.clear();
162 hideEquipmentVisuals();
164}
165
167 const std::string& itemId, const std::string& equipmentSlot, const std::string& layerName,
168 graphics::Texture* texture, int zIndex) {
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();
180 }
181 EquipmentVisual visual;
182 visual.itemId = itemId;
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();
191}
192
194 const std::string& itemId, const std::string& equipmentSlot,
195 graphics::Renderable3D* renderable, const std::string& boneSemanticOrName, float ox, float oy,
196 float oz) {
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;
206 visual.ox = ox;
207 visual.oy = oy;
208 visual.oz = oz;
209 equipmentSignature_.clear();
211 }
212 EquipmentVisual visual;
213 visual.itemId = itemId;
214 visual.equipmentSlot = equipmentSlot;
215 visual.renderable = renderable;
216 visual.boneName = boneSemanticOrName;
217 visual.wearLayer = equipmentSlot;
218 visual.ox = ox;
219 visual.oy = oy;
220 visual.oz = oz;
221 equipmentVisuals_.push_back(std::move(visual));
222 renderable->setVisible(false);
223 equipmentSignature_.clear();
225}
226
228 const std::string& itemId, const std::string& equipmentSlot, int partIndex,
229 const std::string& partName, graphics::Mesh* mesh, graphics::Material* material,
230 animation::AnimSkin* skin) {
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_) {
237 if (part.partIndex == partIndex) return EquipmentVisualChange::Rejected;
238 }
239 for (const auto& visual : equipmentVisuals_) {
240 if (visual.partIndex == partIndex && visual.equipmentSlot != equipmentSlot)
242 }
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);
251 renderable3d_->clearPartSortPriority(visual.partIndex);
252 }
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;
258 visual.skinnedMaterial = material;
259 visual.skinnedSkin = skin;
260 visual.renderable = nullptr;
261 visual.boneName.clear();
262 visual.cpuPositions.clear();
263 equipmentSignature_.clear();
265 }
266 EquipmentVisual visual;
267 visual.itemId = itemId;
268 visual.equipmentSlot = equipmentSlot;
269 visual.layerName = partName;
270 visual.wearLayer = equipmentSlot;
271 visual.partIndex = partIndex;
272 visual.skinnedMesh = mesh;
273 visual.skinnedMaterial = material;
274 visual.skinnedSkin = skin;
275 equipmentVisuals_.push_back(std::move(visual));
276 equipmentSignature_.clear();
278}
279
281 const std::string& parent) {
282 if (name.empty() || name == parent || (!parent.empty() && !equipmentLayers_.count(parent)))
284 auto found = equipmentLayers_.find(name);
285 if (found != equipmentLayers_.end() && found->second.order == order &&
286 found->second.parent == parent)
288 auto& layer = equipmentLayers_[name];
289 layer.order = order;
290 layer.parent = parent;
291 equipmentSignature_.clear();
293}
294
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();
308}
309
311 const std::string& itemId, const std::string& equipmentSlot, const std::string& wearLayer,
312 int withinLayerOrder) {
313 if (!equipmentLayers_.count(wearLayer)) return EquipmentVisualChange::Rejected;
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();
322 }
324}
325
326int AvatarInstance::equipmentLayerOrder(const std::string& name) const {
327 const auto found = equipmentLayers_.find(name);
328 return found == equipmentLayers_.end() ? 0 : found->second.order;
329}
330
331void AvatarInstance::hideEquipmentVisuals() {
332 for (auto& visual : equipmentVisuals_) {
333 visual.updateMode = SkinnedPartUpdateMode::Unavailable;
334 if (kind_ == "image" && !visual.layerName.empty()) {
335 if (Layer* layer = findLayer(visual.layerName)) setLayerVisible(layer->name, false);
336 }
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_)
341 renderable3d_->clearPartSortPriority(visual.partIndex);
342 if (!visual.boneName.empty()) detachAttachment("equipment:" + visual.equipmentSlot);
343 }
344}
345
347 if (!equipment_) return EquipmentVisualChange::Rejected;
348 std::string signature;
349 std::unordered_map<std::string, std::string> equipped;
350 for (int index = 0; index < equipment_->getSlotCount(); ++index) {
351 const std::string slot = equipment_->getSlotName(index);
352 const std::string item = equipment_->getSlotItemId(slot);
353 signature += slot + "=" + item + "#" + std::to_string(equipment_->getSlotInstanceId(slot)) + ";";
354 if (!item.empty()) equipped.emplace(slot, item);
355 }
356 if (signature == equipmentSignature_) return EquipmentVisualChange::Unchanged;
357
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());
369 }
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())
375 continue;
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);
380 setLayerTexture(visual.layerName, visual.texture);
381 setLayerZ(visual.layerName, renderOrder + visual.zIndex);
382 setLayerVisible(visual.layerName, true);
383 }
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_);
390 } else {
391 attachToBone("equipment:" + visual.equipmentSlot, visual.boneName, visual.renderable,
392 visual.ox, visual.oy, visual.oz);
393 }
394 }
395 if (visual.partIndex >= 0 && visual.skinnedMesh && renderable3d_) {
396 renderable3d_->setPart(visual.partIndex, visual.layerName, visual.skinnedMesh,
397 visual.skinnedMaterial);
398 renderable3d_->setPartSortPriority(visual.partIndex, renderOrder);
399 }
400 projectedEquipment_[visual.equipmentSlot] = visual.itemId;
401 equipmentRenderStack_.push_back({visual.itemId, visual.wearLayer, renderOrder});
402 }
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;
407 });
408 equipmentSignature_ = std::move(signature);
410}
411
412std::string AvatarInstance::getEquipmentVisualItem(const std::string& equipmentSlot) const {
413 const auto found = projectedEquipment_.find(equipmentSlot);
414 return found == projectedEquipment_.end() ? std::string{} : found->second;
415}
416
418 return static_cast<int>(equipmentRenderStack_.size());
419}
420
422 return index < 0 || index >= static_cast<int>(equipmentRenderStack_.size())
423 ? std::string{}
424 : equipmentRenderStack_[static_cast<size_t>(index)].itemId;
425}
426
428 return index < 0 || index >= static_cast<int>(equipmentRenderStack_.size())
429 ? std::string{}
430 : equipmentRenderStack_[static_cast<size_t>(index)].wearLayer;
431}
432
433void AvatarInstance::setExpression(const std::string &name) {
434 if (vrm_) {
435 for (const auto& e : vrm_->document.expressions) setParameter(e.name, e.name == name ? 1.f : 0.f);
436 expression_ = name;
437 return;
438 }
439 expression_ = name;
440 if (kind_ == "image") {
442 } else if (kind_ == "live2d") {
443 if (!live2d_) live2d_ = Avatar::createLive2DBackend();
444 if (live2d_) live2d_->setExpression(name);
445 } else if (kind_ == "vroid") {
446 if (expressionDefs_.count(name)) {
448 } else if (!name.empty()) {
449 // Solo morph: zero known morph params then set named weight to 1.
450 if (boundMesh_ && boundMesh_->hasMorphData()) {
451 for (int i = 0; i < boundMesh_->getMorphCount(); ++i) {
452 const std::string mn = boundMesh_->getMorphName(i);
453 ensureParameter(mn, 0.f);
454 }
455 }
456 ensureParameter(name, 1.f);
457 syncMorphWeightsToMesh();
458 }
459 }
460}
461
462void AvatarInstance::setMotion(const std::string &name) {
463 motion_ = name;
464 if (kind_ == "live2d") {
465 if (!live2d_) live2d_ = Avatar::createLive2DBackend();
466 if (live2d_) live2d_->setMotion(name);
467 }
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_);
473 else
474 animPlayer_->play(it->second);
475 hasPreviousRoot_ = false;
476 }
477 if (animStateMachine_) animStateMachine_->setTrigger(name);
478 setParameter("motion:" + name, 1.f);
479 }
480}
481
482void AvatarInstance::setParameter(const std::string &name, float value) {
483 if (name.empty()) return;
484 ensureParameter(name, value);
485 if (kind_ == "live2d") {
486 if (!live2d_) live2d_ = Avatar::createLive2DBackend();
487 if (live2d_) live2d_->setParameter(name, value);
488 } else if (kind_ == "vroid") {
489 if (boundMesh_ && boundMesh_->hasMorph(name)) boundMesh_->setMorphWeight(name, value);
490 } else if (kind_ == "image") {
491 // Parameter name matching a layer drives that layer's alpha (lip-sync etc.).
492 if (Layer *L = findLayer(name)) {
493 float a = value;
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;
499 }
500 }
501}
502
503float AvatarInstance::getParameter(const std::string &name) const {
504 if (kind_ == "live2d" && live2d_) return live2d_->getParameter(name);
505 auto it = parameters_.find(name);
506 return it == parameters_.end() ? 0.f : it->second;
507}
508
509bool AvatarInstance::hasParameter(const std::string &name) const {
510 if (kind_ == "live2d" && live2d_) {
511 // Backend may not expose has; fall through to local cache.
512 }
513 return parameters_.find(name) != parameters_.end();
514}
515
516int AvatarInstance::getParameterCount() const { return int(parameterOrder_.size()); }
517
519 if (index < 0 || size_t(index) >= parameterOrder_.size()) return {};
520 return parameterOrder_[size_t(index)];
521}
522
524 if (equipment_) (void)syncEquipmentAppearance();
525 applyTweenTracks();
526 updateExpressionTransition(dt);
527 if (kind_ == "live2d") {
528 if (!live2d_) live2d_ = Avatar::createLive2DBackend();
529 if (live2d_) live2d_->update(dt);
530 }
531 if (kind_ == "vroid") updateSkeletalAnimation(dt);
532}
533
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;
546}
547
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;
560}
561
563 if (kind_ != "vroid" || vrm_) return false;
564 animLayerMixer_ = mixer;
565 animStateMachine_ = nullptr;
566 animPlayer_ = mixer ? mixer->getBasePlayer() : 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;
574}
575
577 if (kind_ != "vroid" || !stack) {
578 animConstraintStack_.reset();
579 return;
580 }
581 animConstraintStack_ = std::make_unique<animation::AnimConstraintStack>(*stack);
582 animConstraintStack_->setSkeleton(animSkeleton_);
583}
584
586 if (kind_ != "vroid" || !solver) {
587 footIKSolver_.reset();
588 return;
589 }
590 footIKSolver_ = std::make_unique<animation::FootIKSolver>(*solver);
591 footIKSolver_->setSkeleton(animSkeleton_);
592}
593
595 if (kind_ != "vroid") {
596 dynamicBoneSolver_.reset();
597 return;
598 }
599 if (!solver) {
600 dynamicBoneSolver_.reset();
601 return;
602 }
603 dynamicBoneSolver_ = std::make_unique<animation::DynamicBoneSolver>(*solver);
604 if (dynamicBoneSolver_) dynamicBoneSolver_->setSkeleton(animSkeleton_);
605}
606
608 if (kind_ != "vroid" || vrm_) return false;
609 animSkin_ = skin;
610 skinnedPositions_.clear();
611 return skin != nullptr;
612}
613
615 int partIndex, const std::string& partName, graphics::Mesh* mesh,
617 if (kind_ != "vroid" || partIndex < 0 ||
618 partIndex >= graphics::Renderable3D::MeshRenderer::kMaxParts || partName.empty() ||
619 !mesh || !skin)
621 for (const auto& visual : equipmentVisuals_) {
622 if (visual.partIndex == partIndex) return EquipmentVisualChange::Rejected;
623 }
624 for (auto& part : skinnedParts_) {
625 if (part.partIndex != partIndex) continue;
626 if (part.partName == partName && part.mesh == mesh && part.material == material &&
627 part.skin == skin)
629 part.partName = partName;
630 part.mesh = mesh;
631 part.material = material;
632 part.skin = skin;
633 part.cpuPositions.clear();
634 if (!renderable3d_) renderable3d_ = graphics::Renderable3D::create();
635 renderable3d_->setPart(partIndex, partName, mesh, material);
637 }
638 SkinnedPart part;
639 part.partIndex = partIndex;
640 part.partName = partName;
641 part.mesh = mesh;
642 part.material = material;
643 part.skin = skin;
644 skinnedParts_.push_back(std::move(part));
645 if (!renderable3d_) renderable3d_ = graphics::Renderable3D::create();
646 renderable3d_->setPart(partIndex, partName, mesh, material);
648}
649
651 const auto found = std::find_if(skinnedParts_.begin(), skinnedParts_.end(),
652 [&](const SkinnedPart& part) {
653 return part.partIndex == partIndex;
654 });
655 if (found == skinnedParts_.end()) return EquipmentVisualChange::Unchanged;
656 skinnedParts_.erase(found);
657 if (renderable3d_) renderable3d_->setPart(partIndex, {}, nullptr, nullptr);
659}
660
662 for (const auto& part : skinnedParts_) {
663 if (part.partIndex == partIndex) return part.updateMode;
664 }
665 for (const auto& visual : equipmentVisuals_) {
666 if (visual.partIndex == partIndex) return visual.updateMode;
667 }
669}
670
672 if (kind_ != "vroid" || name.empty() || !clip) return false;
673 motions_[name] = clip;
674 return true;
675}
676
677void AvatarInstance::setMotionBlendTime(float seconds) { motionBlendTime_ = std::max(0.f, seconds); }
678
680 applyRootMotion_ = enabled;
681 hasPreviousRoot_ = false;
682}
683
684// ---- image ----
685
686AvatarInstance::Layer *AvatarInstance::findLayer(const std::string &name) {
687 for (auto &L : layers_)
688 if (L.name == name) return &L;
689 return nullptr;
690}
691
692const AvatarInstance::Layer *AvatarInstance::findLayer(const std::string &name) const {
693 for (const auto &L : layers_)
694 if (L.name == name) return &L;
695 return nullptr;
696}
697
698bool AvatarInstance::addLayer(const std::string &name, graphics::Texture *texture, int zIndex) {
699 if (kind_ != "image" || name.empty() || findLayer(name)) return false;
700 Layer L;
701 L.name = name;
702 L.zIndex = zIndex;
703 L.entity = graphics::Renderable2D::create();
704 auto sp = L.entity->sprite();
705 sp->texture = texture;
706 // Layered portraits are UI-style artwork and retain their authored colors
707 // when the scene has no Camera2D ambient light configured.
708 sp->receiveLight = false;
709 if (texture && !texture->hasDeferredFilePixels()) {
710 sp->width = float(texture->getWidth());
711 sp->height = float(texture->getHeight());
712 }
713 layers_.push_back(L);
714 return true;
715}
716
717bool AvatarInstance::setLayerTexture(const std::string &name, graphics::Texture *texture) {
718 Layer *L = findLayer(name);
719 if (!L) return false;
720 auto sp = L->entity->sprite();
721 sp->texture = texture;
722 if (texture && L->autoSize && !texture->hasDeferredFilePixels()) {
723 sp->width = float(texture->getWidth());
724 sp->height = float(texture->getHeight());
725 }
726 return true;
727}
728
729bool AvatarInstance::setLayerVisible(const std::string &name, bool visible) {
730 Layer *L = findLayer(name);
731 if (!L) return false;
732 L->visible = visible;
733 L->entity->sprite()->visible = visible_ && visible;
734 return true;
735}
736
737bool AvatarInstance::setLayerOffset(const std::string &name, float ox, float oy) {
738 Layer *L = findLayer(name);
739 if (!L) return false;
740 L->ox = ox;
741 L->oy = oy;
742 return true;
743}
744
745bool AvatarInstance::setLayerColor(const std::string &name, float r, float g, float b, float a) {
746 Layer *L = findLayer(name);
747 if (!L) return false;
748 L->entity->setColor(r, g, b, a);
749 return true;
750}
751
752bool AvatarInstance::setLayerZ(const std::string &name, int zIndex) {
753 Layer *L = findLayer(name);
754 if (!L) return false;
755 L->zIndex = zIndex;
756 return true;
757}
758
759bool AvatarInstance::setLayerSize(const std::string &name, float w, float h) {
760 Layer *L = findLayer(name);
761 if (!L) return false;
762 L->autoSize = false;
763 L->entity->setSize(w, h);
764 return true;
765}
766
767int AvatarInstance::getLayerCount() const { return int(layers_.size()); }
768
769std::string AvatarInstance::getLayerName(int index) const {
770 if (index < 0 || size_t(index) >= layers_.size()) return {};
771 return layers_[size_t(index)].name;
772}
773
774bool AvatarInstance::hasLayer(const std::string &name) const { return findLayer(name) != nullptr; }
775
777 Layer* L = findLayer(name);
778 return L ? L->entity : nullptr;
779}
780
781bool AvatarInstance::defineExpression(const std::string &name, const std::string &spec) {
782 if ((kind_ != "image" && kind_ != "vroid") || name.empty()) return false;
783 expressionDefs_[name] = spec;
784 return true;
785}
786
787bool AvatarInstance::applyExpressionSpec(const std::string &spec) {
788 if (spec.empty()) return true;
789 std::stringstream ss(spec);
790 std::string part;
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;
799
800 if (kind_ == "vroid") {
801 bool flag = false;
802 if (parseBoolish(val, flag)) {
803 setParameter(key, flag ? 1.f : 0.f);
804 } else {
805 char *end = nullptr;
806 const float f = std::strtof(val.c_str(), &end);
807 if (end && end != val.c_str())
809 else
810 setParameter(key, 1.f);
811 }
812 continue;
813 }
814
815 Layer *L = findLayer(key);
816 if (!L) {
817 // Unknown layer name still recorded as parameter for tooling.
818 setParameter(key, 1.f);
819 continue;
820 }
821 bool flag = false;
822 if (parseBoolish(val, flag)) {
823 L->visible = flag;
824 L->entity->sprite()->visible = visible_ && flag;
825 } else {
826 // Non-bool value: show layer and stash as parameter (texture swap by name).
827 L->visible = true;
828 L->entity->sprite()->visible = visible_;
829 setParameter(key, 1.f);
830 setParameter(key + ".variant", float(std::hash<std::string>{}(val) & 0xffff));
831 }
832 }
833 return true;
834}
835
836bool AvatarInstance::applyExpression(const std::string &name) {
837 if (vrm_) {
838 for (const auto& e : vrm_->document.expressions)
839 if (e.name == name) {
841 return true;
842 }
843 }
844 auto it = expressionDefs_.find(name);
845 if (it == expressionDefs_.end()) return false;
846 expressionBlendFrom_.clear();
847 expressionBlendTo_.clear();
848 expressionBlendDuration_ = 0.f;
849 expression_ = name;
850 const bool ok = applyExpressionSpec(it->second);
851 if (kind_ == "vroid") syncMorphWeightsToMesh();
852 return ok;
853}
854
855bool AvatarInstance::transitionExpression(const std::string& name, float duration) {
856 const auto definition = expressionDefs_.find(name);
857 if (definition == expressionDefs_.end()) return false;
858 if (duration <= 0.f) return applyExpression(name);
859
860 std::unordered_map<std::string, float> targets;
861 if (kind_ == "vroid" && boundMesh_ && boundMesh_->hasMorphData()) {
862 for (int i = 0; i < boundMesh_->getMorphCount(); ++i) targets[boundMesh_->getMorphName(i)] = 0.f;
863 }
864 std::stringstream stream(definition->second);
865 std::string part;
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;
873 bool flag = false;
874 if (parseBoolish(value, flag)) {
875 targets[key] = flag ? 1.f : 0.f;
876 continue;
877 }
878 char* end = nullptr;
879 const float numeric = std::strtof(value.c_str(), &end);
880 if (!end || end == value.c_str() || *end != '\0') return applyExpression(name);
881 targets[key] = numeric;
882 }
883 if (targets.empty()) return applyExpression(name);
884
885 expressionBlendFrom_.clear();
886 expressionBlendTo_ = std::move(targets);
887 for (const auto& [key, target] : expressionBlendTo_) {
888 (void)target;
889 if (kind_ == "image") {
890 const Layer* layer = findLayer(key);
891 expressionBlendFrom_[key] = layer ? layer->entity->sprite()->a : getParameter(key);
892 } else {
893 expressionBlendFrom_[key] = getParameter(key);
894 }
895 }
896 expression_ = name;
897 expressionBlendElapsed_ = 0.f;
898 expressionBlendDuration_ = duration;
899 return true;
900}
901
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);
908 const float from = start == expressionBlendFrom_.end() ? getParameter(key) : start->second;
909 setParameter(key, from + (target - from) * t);
910 }
911 if (t >= 1.f) {
912 expressionBlendFrom_.clear();
913 expressionBlendTo_.clear();
914 expressionBlendDuration_ = 0.f;
915 }
916}
917
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;
929 }
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;
947 }
948}
949
950void AvatarInstance::destroyLayers() {
951 for (Layer &L : layers_) {
952 if (L.entity) {
953 ecs::DestroyEntity(L.entity);
954 L.entity = nullptr;
955 }
956 }
957 layers_.clear();
958}
959
960// ---- live2d ----
961
962bool AvatarInstance::loadLive2DModel(const std::string &path) {
963 if (kind_ != "live2d") return false;
964 if (!live2d_) live2d_ = Avatar::createLive2DBackend();
965 if (!live2d_) return false;
966 return live2d_->loadModel(path);
967}
968
970 if (live2d_) return live2d_->getName();
972}
973
975 if (live2d_) return live2d_->isRuntimeAvailable();
977}
978
979void AvatarInstance::collectLive2DDrawItems(std::vector<graphics::DrawItem2D>& out) {
980 if (kind_ != "live2d" || !visible_) return;
981 if (!live2d_) live2d_ = Avatar::createLive2DBackend();
982 if (live2d_) live2d_->collectDrawItems(out);
983}
984
985} // namespace eve::avatar
LogicalId target
double value
Duration start
LogicalId itemId
bool & active
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float duration
std::string from
const std::string & s
tensor::Graph g
Definition GpuGraph.cpp:7
std::uint32_t key
glm::vec4 clip
double r
float v
std::int32_t second
std::int32_t c
int h
TokenKind kind
std::int32_t parent
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::vector< std::int32_t > order
TileLayer * layer
float f
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
float t
Mesh * mesh
Material * material
bool found
bool visible
uint32_t index
double oy
double ox
Keyframed skeletal animation clip (local TRS tracks per bone). Script type: AnimClip.
Definition AnimClip.h:145
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.
Definition AnimPlayer.h:30
AnimSkeleton * getSkeleton() const override
Borrowed pointer accessor. @ownership Borrowed @lifetime Valid while the owning animation object rema...
Definition AnimPlayer.h:44
void crossFade(AnimClip *clip, float blendSeconds)
Cross-fade to clip over blendSeconds (keeps sampling previous until done).
bool isPlaying() const
True when playing.
Definition AnimPlayer.h:77
void play(AnimClip *clip)
Play.
CPU linear-blend skinning binding for one mesh against an AnimSkeleton.
Definition AnimSkin.h:47
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.
Paired-foot IK with contact smoothing and pelvis compensation.
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.
Definition Avatar.cpp:178
static std::string getLive2DBackendName()
Backend name currently in effect ("null" when using the built-in stub).
Definition Avatar.cpp:170
static ILive2DBackend * createLive2DBackend()
Always returns a backend: custom factory, else NullLive2DBackend.
Definition Avatar.cpp:165
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.
Definition Material.h:35
GPU mesh handle (+ optional CPU morph targets).
Definition Mesh.h:25
bool hasMorphData() const
True when morph data.
Definition Mesh.h:110
bool setMorphWeight(const std::string &name, float weight)
Sets the morph weight.
Definition Mesh.cpp:202
bool hasMorph(const std::string &name) const
True when morph.
Definition Mesh.cpp:198
int getMorphCount() const
Returns the morph count.
Definition Mesh.cpp:191
std::string getMorphName(int index) const
Returns the morph name.
Definition Mesh.cpp:193
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.
Definition Texture.h:18
bool hasDeferredFilePixels() const
True when deferred file pixels.
Definition Texture.h:70
int getWidth() const
Returns the width.
Definition Texture.cpp:28
int getHeight() const
Returns the height.
Definition Texture.cpp:33
EVENGINE_API_FOUNDATION public API.
Definition Equipment.h:19
int getSlotInstanceId(const std::string &slotName) const
Returns the slot instance id.
Definition Equipment.cpp:62
std::string getSlotName(int index) const
Returns the slot name.
Definition Equipment.cpp:39
std::string getSlotItemId(const std::string &slotName) const
Returns the slot item id.
Definition Equipment.cpp:50
int getSlotCount() const
Returns the slot count.
Definition Equipment.cpp:37
EquipmentVisualChange
Outcome of changing or rebuilding the equipment appearance projection.
SkinnedPartUpdateMode
Observable backend selected for a shared-pose skinned part.
bool enabled