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 = falsemeans recursive search (searches child models)bNoChild = truemeans 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:
- Get player model
- If
szHangerandbChildHookare set, get child model (weapon/pet) - Get skeleton hook by name (non-recursive search)
- Calculate position:
- Relative (
bRelHook = true):hookWorldMatrix * offset→ offset in hook's local space - Absolute (
bRelHook = false):modelWorldMatrix * offsetthen translate to hook position
- Relative (
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:
- Transforms offset in model's world space (
pSkin->GetAbsoluteTM() * pOffset) - Subtracts model position
- 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
- Hook Storage: Plan correctly stores hooks in
Dictionary<string, Transform>✅ - Hook Lookup: Plan correctly implements
GetHook()with recursive flag ✅ - Relative Offset: Plan correctly uses
TransformPoint()✅ - Child Model Support: Plan correctly implements
GetChildModel()✅ - Caching: Plan correctly adds hook cache for performance ✅
4.2 ⚠️ Needs Correction
- 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;
}
}
- 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
- Hook Lookup Flag: C++
bNoChild = true→ C#recursive = false✅ - Relative Offset:
TransformPoint()is correct ✅ - Absolute Offset: Needs model transform reference ⚠️
- Child Models: Plan correctly implements
GetChildModel()✅
End of Document