409 lines
13 KiB
Markdown
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**
|