# Add Object to bone

{% embed url="<https://youtu.be/uL6FqGvFruY>" %}

## <mark style="background-color:yellow;">최적화 접근법: "필요한 뼈만 업데이트"</mark>

일반적인 캐릭터 모델에는 수십 또는 수백 개의 뼈가 있지만, 이 중 많은 뼈는 내부 구조용으로만 사용되고 실제로 외부 객체가 부착되지 않습니다. 이 프로젝트에서는 다음 원칙을 적용했습니다:

> "UI상에서 선택한 오브젝트, 뼈들 중 뼈가 아닌 GameObject가 자식으로 들어있는 뼈만 Update하도록 하자!"

이 접근법을 구현하기 위해 사용된 시스템을 분석해 보겠습니다.

### **관련 데이터 구조 (GameObject 클래스)**

```
// GameObject.h의 뼈 관련 필드들
private:
    bool _isBoneObject = false;           // 이 객체가 뼈인지 표시
    bool _hasNonBoneChildren = false;     // 뼈가 아닌 자식이 있는지 여부
    vector<int> _activeBoneIndices;       // 활성화될 뼈 인덱스 목록
    weak_ptr<GameObject> _nonBoneChildrenParent; // 비뼈 객체의 부모 뼈
    weak_ptr<GameObject> _boneParentObject;      // 이 뼈가 속한 부모 객체
    int _boneIndex;                       // 모델의 뼈 배열에서의 인덱스
```

관련 접근자/설정자 메서드:

```
void SetBoneObjectFlag(bool flag) { _isBoneObject = flag; }
bool GetBoneObjectFlag() { return _isBoneObject; }
void SetBoneParentObject(shared_ptr<GameObject> parent) { _boneParentObject = parent; }
weak_ptr<GameObject> GetBoneParentObject() { return _boneParentObject; }
void SetHasNoneBoneChildrenFlag(bool flag) { _hasNonBoneChildren = flag; }
bool GetHasNoneBoneChildrenFlag() { return _hasNonBoneChildren; }
void SetBoneIndex(int index) { _boneIndex = index; }
int GetBoneIndex() { return _boneIndex; }
vector<int> GetActiveBoneIndices() { return _activeBoneIndices; }

void AddActiveBoneIndex(int index) { 
    _activeBoneIndices.push_back(index); 
    // 중복 제거를 위한 정렬 및 유니크 처리
    std::sort(_activeBoneIndices.begin(), _activeBoneIndices.end());
    auto last = std::unique(_activeBoneIndices.begin(), _activeBoneIndices.end());
    _activeBoneIndices.erase(last, _activeBoneIndices.end());
}

void RemoveActiveBoneIndex(int index) { 
    _activeBoneIndices.erase(std::remove(_activeBoneIndices.begin(), 
    _activeBoneIndices.end(), index), _activeBoneIndices.end()); 
}
void SetNonBoneChildrenParent(shared_ptr<GameObject> parent) { _nonBoneChildrenParent = parent; }
shared_ptr<GameObject> GetNoneBoneChildrenParent() { return _nonBoneChildrenParent.lock(); }
```

### **Animator에서의 뼈 업데이트 로직**

Animator::UpdateBoneTransforms() 함수는 성능 최적화의 핵심입니다:

```
void Animator::UpdateBoneTransforms()
{
    if (auto model = _model.lock())  // weak_ptr를 shared_ptr로 변환
    {
        if (!_currClip)
            return;

        shared_ptr<Transition> currTransition = _currClip->transition;
        bool isBlending = (currTransition != nullptr && _blendAnimDesc.blendSumTime > 0.0f);

        // 핵심: 모든 뼈가 아닌 활성화된 뼈만 순회
        for (int boneIndex : GetGameObject()->GetActiveBoneIndices())
        {
            const auto& bone = model->GetBoneByIndex(boneIndex);
            shared_ptr<GameObject> boneObject = FindBoneObjectByIndex(bone->index);
            if (!boneObject || bone->index < 0)
                continue;

            Matrix finalTransform;
            
            // 애니메이션 블렌딩 또는 단일 애니메이션 처리
            // ... [애니메이션 계산 코드] ...
            
            // 최종 변환 행렬 분해 및 적용
            Vec3 scale, translation;
            Quaternion rotation;
            finalTransform.Decompose(scale, rotation, translation);

            auto boneTransform = boneObject->transform();
            boneTransform->SetLocalPosition(translation);
            boneTransform->SetQTLocaslRotation(rotation);
            boneTransform->SetLocalScale(scale);
        }
    }
}
```

이 함수의 핵심은 GetGameObject()->GetActiveBoneIndices()에서 반환하는 뼈 인덱스만 업데이트한다는 점입니다. 모든 뼈가 아닌, 활성화된 뼈만 처리함으로써 상당한 성능 이득을 얻을 수 있습니다.

### **계층 구조 관리와 활성 뼈 등록**

GUIManager::HandleBoneObjectParenting 함수는 계층 구조가 변경될 때 활성 뼈를 관리합니다:

```
void HandleBoneObjectParenting(shared_ptr<GameObject> child, shared_ptr<GameObject> newParent)
{
    if (newParent && newParent->GetBoneObjectFlag())
    {
        // 중요: 뼈 오브젝트에 새로운 자식이 추가될 때 해당 뼈를 활성 뼈 목록에 추가
        newParent->GetBoneParentObject().lock()->AddActiveBoneIndex(newParent->GetBoneIndex());
        _tempBoneIndex = INT_MAX;
        
        // 자식이 뼈가 아닌 경우 특별 처리
        if (!child->GetBoneObjectFlag())
        {
            newParent->SetHasNoneBoneChildrenFlag(true);
            child->SetNonBoneChildrenParent(newParent);
        }
    }
}
```

이 함수는 다음과 같은 중요한 작업을 수행합니다:

1. 자식 객체가 뼈 객체의 하위로 배치될 때 호출됩니다.
2. 부모가 뼈 객체인 경우:
   1. 루트 모델 객체(BoneParentObject)의 활성 뼈 목록에 이 뼈의 인덱스를 추가합니다.
   2. 자식이 뼈가 아니라면 부모 뼈의 \_hasNonBoneChildren 플래그를 설정합니다.
   3. 자식에게 부모 뼈 참조를 설정합니다(SetNonBoneChildrenParent).

### **활성 뼈 인덱스 관리 흐름**

이 시스템의 전체 흐름은 다음과 같습니다:

```
┌─────────────────────┐
│ 모델 로드 및 초기화   │
│ (뼈 GameObject 생성) │
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│ 계층 구조 설정        │
│ (부모-자식 관계)      │
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐      ┌──────────────────────────┐
│ UI에서 객체를 뼈에    │────▶│ HandleBoneObjectParenting│
│ 자식으로 추가         │     │ 호출                      │
└──────────┬──────────┘     └──────────┬────────────────┘
           │                           │
           │                           ▼
           │              ┌───────────────────────────────────────────────┐
           │              │ newParent->GetBoneParentObject().lock()->     │
           │              │ AddActiveBoneIndex(newParent->GetBoneIndex()) │
           │              │ (부모 뼈를 활성 뼈 목록에 추가)                   │
           │              └──────────────┬────────────────────────────────┘
           │                             │
           ▼                            ▼
┌─────────────────────┐      ┌─────────────────────┐
│ 애니메이션 업데이트    │────▶│ UpdateBoneTransforms│
│ (Animator::Update)  │      │ (활성 뼈만 변환)      │
└─────────────────────┘      └─────────────────────┘
```

## <mark style="background-color:yellow;">최적화 효과 분석</mark>

이 접근법은 다음과 같은 중요한 최적화 효과를 제공합니다:

1. 선택적 뼈 업데이트: 모든 뼈가 아닌, 실제로 필요한 뼈만 업데이트합니다.
   1. 일반적인 모델에서 100개의 뼈가 있을 때, 실제로 외부 객체가 붙은 뼈는 10-20개 정도일 수 있습니다.
   2. 90%의 뼈 업데이트 연산을 절약할 수 있습니다.
2. 자동 활성화: 객체가 뼈에 부착될 때 해당 뼈와 그 상위 계층이 자동으로 활성화됩니다.
   1. 수동으로 어떤 뼈를 업데이트할지 관리할 필요가 없습니다.
3. 계층적 관계 유지: 실제 계층 구조는 그대로 유지하면서 업데이트만 최적화합니다.
   1. 이를 통해 애니메이션 품질 저하 없이 성능 향상을 얻을 수 있습니다.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://jihoon-jungs-organization.gitbook.io/jihoon_engine/feature-and-description/add-object-to-bone.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
