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

409 lines
13 KiB
Markdown

# 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)
```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)
```cpp
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):**
```cpp
// 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):**
```cpp
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):**
```cpp
// 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:**
```cpp
vPos = pHook->GetAbsoluteTM() * pOffset;
```
**C# Equivalent (from plan):**
```csharp
vPos = hookTransform.TransformPoint(offset); // ✅ CORRECT
```
**C++ Absolute Offset:**
```cpp
vPos = pSkin->GetAbsoluteTM() * pOffset;
vPos = vPos - pSkin->GetAbsoluteTM().GetRow(3) + pHook->GetAbsoluteTM().GetRow(3);
```
**C# Equivalent (from plan):**
```csharp
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:**
```csharp
// 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):**
```csharp
// 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:**
```csharp
Vector3 hookWorldPos = hookTransform.position;
Vector3 offsetWorld = hookTransform.TransformDirection(offset);
return hookWorldPos + offsetWorld;
```
**Should Be:**
```csharp
// 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:**
```csharp
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;
}
}
```
2. **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:**
```csharp
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:**
```csharp
// 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**