Files
test/Documentation/HOOK_SYSTEM_C++_REFERENCE.md
T
2026-03-13 16:03:47 +07:00

13 KiB

Hook System C++ Reference & Conversion Verification

This document shows the actual C++ implementation of the Hook System and verifies the conversion plan matches it correctly.

Date Created: 2026-02-24


1. C++ Hook System Architecture

1.1 Core Classes

A3DSkeletonHook (Angelica2/Angelica3D/Source/A3DSkeleton.cpp)

class A3DSkeletonHook {
    A3DSkeleton* m_pA3DSkeleton;      // Parent skeleton
    int m_iBone;                      // Bone index (-1 = root)
    A3DMATRIX4 m_matHookTM;          // Local hook transform (relative to bone)
    A3DMATRIX4 m_matScaledHookTM;    // Scaled hook transform
    A3DMATRIX4 m_matAbs;              // Cached absolute world matrix
    A3DMATRIX4 m_matNoScaleAbs;       // Cached absolute matrix (no scale)
    bool m_bFixDir;                   // Fix direction to skeleton
    DWORD m_dwUpdateCnt;              // Update counter
    
    // Get world transform matrix
    const A3DMATRIX4& GetAbsoluteTM() {
        Update(false);  // Update if needed
        return m_matAbs;
    }
    
    // Update hook transform (called automatically)
    void Update(bool bForce) {
        if (m_iBone < 0) {
            // Root bone hook
            m_matAbs = m_matScaledHookTM * m_pA3DSkeleton->GetAbsoluteTM();
        } else {
            // Bone-attached hook
            A3DBone* pBone = m_pA3DSkeleton->GetBone(m_iBone);
            m_matAbs = matWholeScale * (m_matScaledHookTM * pBone->GetNoScaleAbsTM());
        }
    }
};

Key Points:

  • Hook has local transform (m_matHookTM) relative to bone
  • GetAbsoluteTM() returns world-space transform matrix
  • Automatically updates when skeleton animates
  • Supports bone scaling

A3DSkinModel::GetSkeletonHook() (A3DSkinModel.cpp:2357)

A3DSkeletonHook* A3DSkinModel::GetSkeletonHook(const char* szName, bool bNoChild)
{
    A3DSkeletonHook* pHook = NULL;
    
    // Search in main skeleton
    if (m_pA3DSkeleton) {
        if ((pHook = m_pA3DSkeleton->GetHook(szName, NULL)))
            return pHook;
    }
    
    // Search in child models (if bNoChild == false, recursive)
    if (!bNoChild) {
        for (int i = 0; i < m_aChildModels.GetSize(); i++) {
            A3DSkinModel* pChild = m_aChildModels[i];
            if ((pHook = pChild->GetSkeletonHook(szName, false)))
                return pHook;
        }
    }
    
    return NULL;
}

Key Points:

  • bNoChild = false means recursive search (searches child models)
  • bNoChild = true means non-recursive (only main skeleton)
  • Searches child models (weapons, pets) if recursive

2. C++ Hook Usage in Skill GFX

2.1 Player Hook Lookup (CECSkillGfxEvent::get_pos_by_id)

C++ Code (from commented C# code):

// Player branch
CECModel* pModel = pPlayer->GetPlayerModel();
if (!pModel)
    break;

// Handle child model (hanger) if specified
if (szHanger && bChildHook)
    pModel = pModel->GetChildModel(szHanger);

if (!pModel)
    break;

A3DSkinModel* pSkin = pModel->GetA3DSkinModel();
A3DSkeletonHook* pHook = pSkin->GetSkeletonHook(szHook, true);  // true = non-recursive

if (!pHook)
    break;

// Calculate position
if (bRelHook) {
    // Relative offset: transform offset in hook's local space
    vPos = pHook->GetAbsoluteTM() * pOffset;  // Matrix * Vector3
} else {
    // Absolute offset: offset in model space, then translate to hook position
    vPos = pSkin->GetAbsoluteTM() * pOffset;
    vPos = vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3);
}

return true;

Logic Breakdown:

  1. Get player model
  2. If szHanger and bChildHook are set, get child model (weapon/pet)
  3. Get skeleton hook by name (non-recursive search)
  4. Calculate position:
    • Relative (bRelHook = true): hookWorldMatrix * offset → offset in hook's local space
    • Absolute (bRelHook = false): modelWorldMatrix * offset then translate to hook position

2.2 NPC Hook Lookup (CECNPC::GetSgcHook)

C++ Code (EC_NPCModel.cpp:505):

A3DSkeletonHook* CECNPCModelDefaultPolicy::GetSgcHook(
    const char* szHanger, 
    bool bChildHook, 
    const char* szHook) 
{
    if (A3DSkinModel* pSkinModel = GetSgcSkinModel(szHanger, bChildHook, szHook)) {
        return pSkinModel->GetSkeletonHook(szHook, true);  // true = non-recursive
    }
    return NULL;
}

Usage in get_pos_by_id (NPC branch):

// NPC branch
A3DSkeletonHook* pHook = pNPC->GetSgcHook(szHanger, bChildHook, szHook);
if (!pHook)
    break;
    
A3DSkinModel* pSkin = pNPC->GetSgcSkinModel(szHanger, bChildHook, szHook);
if (bRelHook) {
    vPos = pHook->GetAbsoluteTM() * pOffset;
} else {
    vPos = pSkin->GetAbsoluteTM() * pOffset;
    vPos = vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3);
}
return true;

3. C++ to C# Conversion Mapping

3.1 Transform Matrix Operations

C++ Operation C# Equivalent Notes
pHook->GetAbsoluteTM() hookTransform (Unity Transform) World transform
pHook->GetAbsoluteTM() * pOffset hookTransform.TransformPoint(offset) Relative offset
pSkin->GetAbsoluteTM() * pOffset skinTransform.TransformPoint(offset) Model-space offset
pHook->GetAbsoluteTM().GetRow(3) hookTransform.position Hook world position
pSkin->GetAbsoluteTM().GetRow(3) skinTransform.position Model world position

3.2 Hook Lookup

C++ C# (Plan) Status
pSkin->GetSkeletonHook(szHook, true) skeletonBuilder.GetHook(szHook, false) ⚠️ INVERTED FLAG
pModel->GetChildModel(szHanger) pModel.GetChildModel(szHanger) Matches
pNPC->GetSgcHook(...) pNPC.GetHook(...) Simplified

⚠️ IMPORTANT: Flag Inversion

C++ GetSkeletonHook(szHook, true) means non-recursive (don't search children).
C# plan uses GetHook(szHook, false) which means non-recursive (don't recurse).

The conversion plan is CORRECT - Unity's recursive = false matches C++'s bNoChild = true.

3.3 Position Calculation

C++ Relative Offset:

vPos = pHook->GetAbsoluteTM() * pOffset;

C# Equivalent (from plan):

vPos = hookTransform.TransformPoint(offset);  // ✅ CORRECT

C++ Absolute Offset:

vPos = pSkin->GetAbsoluteTM() * pOffset;
vPos = vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3);

C# Equivalent (from plan):

Vector3 hookWorldPos = hookTransform.position;
Vector3 offsetWorld = hookTransform.TransformDirection(offset);
vPos = hookWorldPos + offsetWorld;  // ⚠️ NEEDS VERIFICATION

Verification Needed: The C++ absolute offset logic:

  1. Transforms offset in model's world space (pSkin->GetAbsoluteTM() * pOffset)
  2. Subtracts model position
  3. Adds hook position

This is equivalent to: offset in model's local space, then translate to hook position.

C# Correct Implementation:

// Get model transform (equivalent to pSkin)
Transform modelTransform = pModel.transform;  // Or get from SkeletonBuilder root

// Transform offset in model's world space
Vector3 offsetWorld = modelTransform.TransformPoint(offset);

// Translate to hook position
vPos = offsetWorld - modelTransform.position + hookTransform.position;

OR simpler (if offset is in model local space):

// Transform offset from model local to world
Vector3 offsetWorld = modelTransform.TransformDirection(offset);
// Add hook position
vPos = hookTransform.position + offsetWorld;

4. Conversion Plan Verification

4.1 Correct Aspects

  1. Hook Storage: Plan correctly stores hooks in Dictionary<string, Transform>
  2. Hook Lookup: Plan correctly implements GetHook() with recursive flag
  3. Relative Offset: Plan correctly uses TransformPoint()
  4. Child Model Support: Plan correctly implements GetChildModel()
  5. Caching: Plan correctly adds hook cache for performance

4.2 ⚠️ Needs Correction

  1. Absolute Offset Calculation (HookUtils.cs:400-409)

Current Plan:

Vector3 hookWorldPos = hookTransform.position;
Vector3 offsetWorld = hookTransform.TransformDirection(offset);
return hookWorldPos + offsetWorld;

Should Be:

// Need model transform reference
// C++: pSkin->GetAbsoluteTM() * pOffset
Vector3 offsetWorld = modelTransform.TransformPoint(offset);
// C++: vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3)
return offsetWorld - modelTransform.position + hookTransform.position;

Problem: HookUtils.GetHookWorldPosition() doesn't have access to model transform.

Solution: Pass model transform as parameter, OR get it from hook's root parent.

Updated HookUtils:

public static Vector3 GetHookWorldPosition(
    Transform hookTransform, 
    Vector3 offset, 
    bool bRelative,
    Transform modelTransform = null)  // Add model transform parameter
{
    if (hookTransform == null)
        return Vector3.zero;
    
    if (bRelative) {
        return hookTransform.TransformPoint(offset);
    } else {
        // Absolute offset: transform in model space, then translate to hook
        if (modelTransform == null) {
            // Fallback: use hook's root (skeleton root)
            modelTransform = hookTransform.root;
        }
        
        Vector3 offsetWorld = modelTransform.TransformPoint(offset);
        return offsetWorld - modelTransform.position + hookTransform.position;
    }
}
  1. GetSkeletonHook Flag Usage

C++: GetSkeletonHook(szHook, true) = non-recursive
Plan: GetHook(szHook, true) = recursive (inverted!)

Correction: The plan's recursive parameter should match C++'s bNoChild:

  • C++ bNoChild = true → C# recursive = false
  • C++ bNoChild = false → C# recursive = true

Current plan uses recursive = true in calls, which is CORRECT (matches C++ bNoChild = false for recursive search).

Verification:

  • C++: GetSkeletonHook(szHook, true) = non-recursive (only main skeleton)
  • Plan: GetHook(szHook, false) = non-recursive CORRECT

5. Updated Conversion Plan Corrections

5.1 Fix HookUtils.GetHookWorldPosition()

File: Assets/PerfectWorld/Scripts/Utility/HookUtils.cs

Update absolute offset calculation:

public static Vector3 GetHookWorldPosition(
    Transform hookTransform, 
    Vector3 offset, 
    bool bRelative,
    Transform modelTransform = null)  // Add optional model transform
{
    if (hookTransform == null)
        return Vector3.zero;
    
    if (bRelative) {
        // Relative offset: transform offset from hook's local space to world space
        // C++: pHook->GetAbsoluteTM() * pOffset
        return hookTransform.TransformPoint(offset);
    } else {
        // Absolute offset: transform offset in model space, then translate to hook position
        // C++: vPos = pSkin->GetAbsoluteTM() * pOffset;
        //      vPos = vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3);
        
        // Get model transform (skeleton root or provided)
        if (modelTransform == null) {
            // Fallback: use hook's root bone (skeleton root)
            // Find SkeletonBuilder component in hierarchy
            SkeletonBuilder skeleton = hookTransform.GetComponentInParent<SkeletonBuilder>();
            modelTransform = skeleton != null ? skeleton.rootBone : hookTransform.root;
        }
        
        // Transform offset in model's world space
        Vector3 offsetWorld = modelTransform.TransformPoint(offset);
        
        // Translate to hook position
        return offsetWorld - modelTransform.position + hookTransform.position;
    }
}

5.2 Update get_pos_by_id() Calls

File: Assets/PerfectWorld/Scripts/Managers/CECSkillGfxMan.cs

Pass model transform for absolute offset:

// In player branch:
CECModel pModel = pPlayer.GetPlayerModel();
Transform modelTransform = pModel.transform;  // Or get from SkeletonBuilder

Transform pHook = pModel.GetHook(szHook, true);
vPos = HookUtils.GetHookWorldPosition(pHook, pOffset, bRelHook, modelTransform);

6. Summary

Conversion Plan is Mostly Correct

The conversion plan correctly:

  • Maps C++ hook lookup to Unity Transform system
  • Implements recursive search correctly
  • Handles relative offset correctly
  • Supports child models

⚠️ One Correction Needed

Absolute Offset Calculation:

  • Current plan uses TransformDirection() which is incorrect
  • Should use TransformPoint() on model transform, then translate
  • Need to pass model transform to HookUtils.GetHookWorldPosition()

📝 Implementation Notes

  1. Hook Lookup Flag: C++ bNoChild = true → C# recursive = false
  2. Relative Offset: TransformPoint() is correct
  3. Absolute Offset: Needs model transform reference ⚠️
  4. Child Models: Plan correctly implements GetChildModel()

End of Document