From f03b4eec4c37eab75a5bd639279cfcc615105b01 Mon Sep 17 00:00:00 2001
From: aap <aap@papnet.eu>
Date: Thu, 23 Apr 2020 22:25:18 +0200
Subject: [PATCH] implemented skinned peds, no cutscene hands yet

---
 librw                              |   2 +-
 premake5.lua                       |   2 +-
 src/animation/AnimBlendClumpData.h |   4 +-
 src/animation/Bones.cpp            |  52 ++++
 src/animation/Bones.h              |  24 ++
 src/animation/FrameUpdate.cpp      | 243 ++++++++++++++--
 src/animation/RpAnimBlend.cpp      | 114 +++++++-
 src/animation/RpAnimBlend.h        |   5 +-
 src/control/Script.cpp             |   8 +-
 src/core/AnimViewer.cpp            |   7 +-
 src/core/Cam.cpp                   |  13 +-
 src/core/Debug.cpp                 |  37 ++-
 src/core/Debug.h                   |  11 +
 src/core/World.cpp                 |   5 +
 src/core/common.h                  |  11 +
 src/core/config.h                  |   1 +
 src/core/main.cpp                  |   1 +
 src/entities/Entity.cpp            |  70 ++++-
 src/entities/Entity.h              |   4 +
 src/fakerw/fake.cpp                |  46 ++-
 src/fakerw/rphanim.h               |  36 +++
 src/fakerw/rpskin.h                |  18 ++
 src/fakerw/rtcharse.h              |  14 +
 src/fakerw/rtquat.h                |  10 +
 src/math/Quaternion.h              |  12 +
 src/modelinfo/ClumpModelInfo.cpp   |  72 ++++-
 src/modelinfo/PedModelInfo.cpp     | 164 ++++++++++-
 src/modelinfo/PedModelInfo.h       |  17 +-
 src/objects/CutsceneHead.cpp       |  95 ++++++-
 src/objects/CutsceneHead.h         |   9 +
 src/objects/CutsceneObject.cpp     |  65 ++++-
 src/objects/CutsceneObject.h       |  16 ++
 src/peds/CivilianPed.h             |   2 +
 src/peds/CopPed.cpp                |   3 +-
 src/peds/CopPed.h                  |   2 +
 src/peds/EmergencyPed.h            |   2 +
 src/peds/Ped.cpp                   | 246 +++++++++++-----
 src/peds/Ped.h                     |  57 +++-
 src/peds/PedIK.cpp                 | 433 +++++++++++++++++++++--------
 src/peds/PedIK.h                   |   2 +
 src/peds/PlayerPed.cpp             |   6 +
 src/peds/PlayerPed.h               |   2 +
 src/render/SpecialFX.cpp           |  10 +-
 src/rw/RwHelper.cpp                | 179 +++++++++++-
 src/rw/RwHelper.h                  |   9 +
 src/rw/VisibilityPlugins.cpp       |  15 +
 src/rw/VisibilityPlugins.h         |   3 +
 src/skel/win/win.cpp               |   8 +
 src/weapons/Weapon.cpp             |   3 +-
 49 files changed, 1869 insertions(+), 301 deletions(-)
 create mode 100644 src/animation/Bones.cpp
 create mode 100644 src/animation/Bones.h
 create mode 100644 src/fakerw/rtcharse.h
 create mode 100644 src/fakerw/rtquat.h

diff --git a/librw b/librw
index 752fceb1..374f951d 160000
--- a/librw
+++ b/librw
@@ -1 +1 @@
-Subproject commit 752fceb1e3c4ce06b6f20b9c4471927821469bf2
+Subproject commit 374f951d7cee353914059d8ddf9c5aff7d764984
diff --git a/premake5.lua b/premake5.lua
index 6037b01c..14aab9d8 100644
--- a/premake5.lua
+++ b/premake5.lua
@@ -62,7 +62,7 @@ workspace "re3"
 	filter "configurations:DebugRW or ReleaseRW"
 		includedirs { "rwsdk/include/d3d8" }
 		libdirs { "rwsdk/lib/d3d8/release" }
-		links { "rwcore", "rpworld", "rpmatfx", "rpskin", "rphanim", "rtbmp" }
+		links { "rwcore", "rpworld", "rpmatfx", "rpskin", "rphanim", "rtbmp", "rtquat", "rtcharse" }
 	filter  {}
 
 	
diff --git a/src/animation/AnimBlendClumpData.h b/src/animation/AnimBlendClumpData.h
index 1c8c391d..a537425a 100644
--- a/src/animation/AnimBlendClumpData.h
+++ b/src/animation/AnimBlendClumpData.h
@@ -18,7 +18,7 @@ struct AnimBlendFrameData
 #ifdef PED_SKIN
 	union {
 		RwFrame *frame;
-		RpHAnimStdKeyFrame *hanimframe;
+		RpHAnimStdKeyFrame *hanimFrame;
 	};
 	int32 nodeID;
 #else
@@ -50,4 +50,6 @@ public:
 #endif
 	void ForAllFrames(void (*cb)(AnimBlendFrameData*, void*), void *arg);
 };
+#ifndef PED_SKIN
 static_assert(sizeof(CAnimBlendClumpData) == 0x14, "CAnimBlendClumpData: error");
+#endif
diff --git a/src/animation/Bones.cpp b/src/animation/Bones.cpp
new file mode 100644
index 00000000..1608449d
--- /dev/null
+++ b/src/animation/Bones.cpp
@@ -0,0 +1,52 @@
+#include "common.h"
+#include "PedModelInfo.h"
+#include "Bones.h"
+
+#ifdef PED_SKIN
+
+int
+ConvertPedNode2BoneTag(int node)
+{
+	switch(node){
+	case PED_TORSO:	return BONE_waist;
+	case PED_MID:	return BONE_torso;	// this is what Xbox/Mobile use
+	//		return BONE_mid;	// this is what PS2/PC use
+	case PED_HEAD:		return BONE_head;
+	case PED_UPPERARML:	return BONE_upperarml;
+	case PED_UPPERARMR:	return BONE_upperarmr;
+	case PED_HANDL:		return BONE_Lhand;
+	case PED_HANDR:		return BONE_Rhand;
+	case PED_UPPERLEGL:	return BONE_upperlegl;
+	case PED_UPPERLEGR:	return BONE_upperlegr;
+	case PED_FOOTL:		return BONE_footl;
+	case PED_FOOTR:		return BONE_footr;
+	case PED_LOWERLEGR:	return BONE_lowerlegl;
+	}
+	return -1;
+}
+
+const char*
+ConvertBoneTag2BoneName(int tag)
+{
+	switch(tag){
+	case BONE_waist:	return "Swaist";
+	case BONE_upperlegr:	return "Supperlegr";
+	case BONE_lowerlegr:	return "Slowerlegr";
+	case BONE_footr:	return "Sfootr";
+	case BONE_upperlegl:	return "Supperlegl";
+	case BONE_lowerlegl:	return "Slowerlegl";
+	case BONE_footl:	return "Sfootl";
+	case BONE_mid:	return "Smid";
+	case BONE_torso:	return "Storso";
+	case BONE_head:	return "Shead";
+	case BONE_upperarmr:	return "Supperarmr";
+	case BONE_lowerarmr:	return "Slowerarmr";
+	case BONE_Rhand:	return "SRhand";
+	case BONE_upperarml:	return "Supperarml";
+	case BONE_lowerarml:	return "Slowerarml";
+	case BONE_Lhand:	return "SLhand";
+	}
+	return nil;
+}
+
+#endif
diff --git a/src/animation/Bones.h b/src/animation/Bones.h
new file mode 100644
index 00000000..38d91ba3
--- /dev/null
+++ b/src/animation/Bones.h
@@ -0,0 +1,24 @@
+#pragma once
+
+enum BoneTag
+{
+	BONE_waist,
+	BONE_upperlegr,
+	BONE_lowerlegr,
+	BONE_footr,
+	BONE_upperlegl,
+	BONE_lowerlegl,
+	BONE_footl,
+	BONE_mid,
+	BONE_torso,
+	BONE_head,
+	BONE_upperarmr,
+	BONE_lowerarmr,
+	BONE_Rhand,
+	BONE_upperarml,
+	BONE_lowerarml,
+	BONE_Lhand,
+};
+
+int ConvertPedNode2BoneTag(int node);
+const char *ConvertBoneTag2BoneName(int tag);
diff --git a/src/animation/FrameUpdate.cpp b/src/animation/FrameUpdate.cpp
index df45bfb5..a38ebc74 100644
--- a/src/animation/FrameUpdate.cpp
+++ b/src/animation/FrameUpdate.cpp
@@ -8,12 +8,18 @@
 
 CAnimBlendClumpData *gpAnimBlendClump;
 
-void FrameUpdateCallBack(AnimBlendFrameData *frame, void *arg);
-void FrameUpdateCallBackWithVelocityExtraction(AnimBlendFrameData *frame, void *arg);
-void FrameUpdateCallBackWith3dVelocityExtraction(AnimBlendFrameData *frame, void *arg);
+// PS2 names without "NonSkinned"
+void FrameUpdateCallBackNonSkinned(AnimBlendFrameData *frame, void *arg);
+void FrameUpdateCallBackNonSkinnedWithVelocityExtraction(AnimBlendFrameData *frame, void *arg);
+void FrameUpdateCallBackNonSkinnedWith3dVelocityExtraction(AnimBlendFrameData *frame, void *arg);
+
+void FrameUpdateCallBackSkinned(AnimBlendFrameData *frame, void *arg);
+void FrameUpdateCallBackSkinnedWithVelocityExtraction(AnimBlendFrameData *frame, void *arg);
+void FrameUpdateCallBackSkinnedWith3dVelocityExtraction(AnimBlendFrameData *frame, void *arg);
+
 
 void
-FrameUpdateCallBack(AnimBlendFrameData *frame, void *arg)
+FrameUpdateCallBackNonSkinned(AnimBlendFrameData *frame, void *arg)
 {
 	CVector vec, pos(0.0f, 0.0f, 0.0f);
 	CQuaternion q, rot(0.0f, 0.0f, 0.0f, 0.0f);
@@ -25,9 +31,9 @@ FrameUpdateCallBack(AnimBlendFrameData *frame, void *arg)
 	if(frame->flag & AnimBlendFrameData::VELOCITY_EXTRACTION &&
 	   gpAnimBlendClump->velocity){
 		if(frame->flag & AnimBlendFrameData::VELOCITY_EXTRACTION_3D)
-			FrameUpdateCallBackWith3dVelocityExtraction(frame, arg);
+			FrameUpdateCallBackNonSkinnedWith3dVelocityExtraction(frame, arg);
 		else
-			FrameUpdateCallBackWithVelocityExtraction(frame, arg);
+			FrameUpdateCallBackNonSkinnedWithVelocityExtraction(frame, arg);
 		return;
 	}
 
@@ -48,12 +54,7 @@ FrameUpdateCallBack(AnimBlendFrameData *frame, void *arg)
 
 	if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){
 		RwMatrixSetIdentity(mat);
-
-		float norm = rot.MagnitudeSqr();
-		if(norm == 0.0f)
-			rot.w = 1.0f;
-		else
-			rot *= 1.0f/Sqrt(norm);
+		rot.Normalise();
 		rot.Get(mat);
 	}
 
@@ -69,7 +70,7 @@ FrameUpdateCallBack(AnimBlendFrameData *frame, void *arg)
 }
 
 void
-FrameUpdateCallBackWithVelocityExtraction(AnimBlendFrameData *frame, void *arg)
+FrameUpdateCallBackNonSkinnedWithVelocityExtraction(AnimBlendFrameData *frame, void *arg)
 {
 	CVector vec, pos(0.0f, 0.0f, 0.0f);
 	CQuaternion q, rot(0.0f, 0.0f, 0.0f, 0.0f);
@@ -122,12 +123,7 @@ FrameUpdateCallBackWithVelocityExtraction(AnimBlendFrameData *frame, void *arg)
 
 	if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){
 		RwMatrixSetIdentity(mat);
-
-		float norm = rot.MagnitudeSqr();
-		if(norm == 0.0f)
-			rot.w = 1.0f;
-		else
-			rot *= 1.0f/Sqrt(norm);
+		rot.Normalise();
 		rot.Get(mat);
 	}
 
@@ -154,7 +150,7 @@ FrameUpdateCallBackWithVelocityExtraction(AnimBlendFrameData *frame, void *arg)
 
 // original code uses do loops?
 void
-FrameUpdateCallBackWith3dVelocityExtraction(AnimBlendFrameData *frame, void *arg)
+FrameUpdateCallBackNonSkinnedWith3dVelocityExtraction(AnimBlendFrameData *frame, void *arg)
 {
 	CVector vec, pos(0.0f, 0.0f, 0.0f);
 	CQuaternion q, rot(0.0f, 0.0f, 0.0f, 0.0f);
@@ -201,12 +197,7 @@ FrameUpdateCallBackWith3dVelocityExtraction(AnimBlendFrameData *frame, void *arg
 
 	if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){
 		RwMatrixSetIdentity(mat);
-
-		float norm = rot.MagnitudeSqr();
-		if(norm == 0.0f)
-			rot.w = 1.0f;
-		else
-			rot *= 1.0f/Sqrt(norm);
+		rot.Normalise();
 		rot.Get(mat);
 	}
 
@@ -220,3 +211,203 @@ FrameUpdateCallBackWith3dVelocityExtraction(AnimBlendFrameData *frame, void *arg
 	}
 	RwMatrixUpdate(mat);
 }
+
+#ifdef PED_SKIN
+
+void
+FrameUpdateCallBackSkinned(AnimBlendFrameData *frame, void *arg)
+{
+	CVector vec, pos(0.0f, 0.0f, 0.0f);
+	CQuaternion q, rot(0.0f, 0.0f, 0.0f, 0.0f);
+	float totalBlendAmount = 0.0f;
+	RpHAnimStdKeyFrame *xform = frame->hanimFrame;
+	CAnimBlendNode **node;
+	AnimBlendFrameUpdateData *updateData = (AnimBlendFrameUpdateData*)arg;
+
+	if(frame->flag & AnimBlendFrameData::VELOCITY_EXTRACTION &&
+	   gpAnimBlendClump->velocity){
+		if(frame->flag & AnimBlendFrameData::VELOCITY_EXTRACTION_3D)
+			FrameUpdateCallBackSkinnedWith3dVelocityExtraction(frame, arg);
+		else
+			FrameUpdateCallBackSkinnedWithVelocityExtraction(frame, arg);
+		return;
+	}
+
+	if(updateData->foobar)
+		for(node = updateData->nodes; *node; node++)
+			if((*node)->sequence && (*node)->association->IsPartial())
+				totalBlendAmount += (*node)->association->blendAmount;
+
+	for(node = updateData->nodes; *node; node++){
+		if((*node)->sequence){
+			(*node)->Update(vec, q, 1.0f-totalBlendAmount);
+			if((*node)->sequence->HasTranslation())
+				pos += vec;
+			rot += q;
+		}
+		++*node;
+	}
+
+	if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){
+		rot.Normalise();
+		xform->q.imag.x = rot.x;
+		xform->q.imag.y = rot.y;
+		xform->q.imag.z = rot.z;
+		xform->q.real = rot.w;
+	}
+
+	if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){
+		xform->t.x = pos.x;
+		xform->t.y = pos.y;
+		xform->t.z = pos.z;
+		xform->t.x += frame->resetPos.x;
+		xform->t.y += frame->resetPos.y;
+		xform->t.z += frame->resetPos.z;
+	}
+}
+
+void
+FrameUpdateCallBackSkinnedWithVelocityExtraction(AnimBlendFrameData *frame, void *arg)
+{
+	CVector vec, pos(0.0f, 0.0f, 0.0f);
+	CQuaternion q, rot(0.0f, 0.0f, 0.0f, 0.0f);
+	float totalBlendAmount = 0.0f;
+	float transx = 0.0f, transy = 0.0f;
+	float curx = 0.0f, cury = 0.0f;
+	float endx = 0.0f, endy = 0.0f;
+	bool looped = false;
+	RpHAnimStdKeyFrame *xform = frame->hanimFrame;
+	CAnimBlendNode **node;
+	AnimBlendFrameUpdateData *updateData = (AnimBlendFrameUpdateData*)arg;
+
+	if(updateData->foobar)
+		for(node = updateData->nodes; *node; node++)
+			if((*node)->sequence && (*node)->association->IsPartial())
+				totalBlendAmount += (*node)->association->blendAmount;
+
+	for(node = updateData->nodes; *node; node++)
+		if((*node)->sequence && (*node)->sequence->HasTranslation()){
+			if((*node)->association->HasTranslation()){
+				(*node)->GetCurrentTranslation(vec, 1.0f-totalBlendAmount);
+				cury += vec.y;
+				if((*node)->association->HasXTranslation())
+					curx += vec.x;
+			}
+		}
+
+	for(node = updateData->nodes; *node; node++){
+		if((*node)->sequence){
+			bool nodelooped = (*node)->Update(vec, q, 1.0f-totalBlendAmount);
+			rot += q;
+			if((*node)->sequence->HasTranslation()){
+				pos += vec;
+				if((*node)->association->HasTranslation()){
+					transy += vec.y;
+					if((*node)->association->HasXTranslation())
+						transx += vec.x;
+					looped |= nodelooped;
+					if(nodelooped){
+						(*node)->GetEndTranslation(vec, 1.0f-totalBlendAmount);
+						endy += vec.y;
+						if((*node)->association->HasXTranslation())
+							endx += vec.x;
+					}
+				}
+			}
+		}
+		++*node;
+	}
+
+	if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){
+		rot.Normalise();
+		xform->q.imag.x = rot.x;
+		xform->q.imag.y = rot.y;
+		xform->q.imag.z = rot.z;
+		xform->q.real = rot.w;
+	}
+
+	if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){
+		gpAnimBlendClump->velocity->x = transx - curx;
+		gpAnimBlendClump->velocity->y = transy - cury;
+		if(looped){
+			gpAnimBlendClump->velocity->x += endx;
+			gpAnimBlendClump->velocity->y += endy;
+		}
+		xform->t.x = pos.x - transx;
+		xform->t.y = pos.y - transy;
+		xform->t.z = pos.z;
+		if(xform->t.z >= -0.8f)
+			if(xform->t.z < -0.4f)
+				xform->t.z += (2.5f * xform->t.z + 2.0f) * frame->resetPos.z;
+			else
+				xform->t.z += frame->resetPos.z;
+		xform->t.x += frame->resetPos.x;
+		xform->t.y += frame->resetPos.y;
+	}
+}
+
+void
+FrameUpdateCallBackSkinnedWith3dVelocityExtraction(AnimBlendFrameData *frame, void *arg)
+{
+	CVector vec, pos(0.0f, 0.0f, 0.0f);
+	CQuaternion q, rot(0.0f, 0.0f, 0.0f, 0.0f);
+	float totalBlendAmount = 0.0f;
+	CVector trans(0.0f, 0.0f, 0.0f);
+	CVector cur(0.0f, 0.0f, 0.0f);
+	CVector end(0.0f, 0.0f, 0.0f);
+	bool looped = false;
+	RpHAnimStdKeyFrame *xform = frame->hanimFrame;
+	CAnimBlendNode **node;
+	AnimBlendFrameUpdateData *updateData = (AnimBlendFrameUpdateData*)arg;
+
+	if(updateData->foobar)
+		for(node = updateData->nodes; *node; node++)
+			if((*node)->sequence && (*node)->association->IsPartial())
+				totalBlendAmount += (*node)->association->blendAmount;
+
+	for(node = updateData->nodes; *node; node++)
+		if((*node)->sequence && (*node)->sequence->HasTranslation()){
+			if((*node)->association->HasTranslation()){
+				(*node)->GetCurrentTranslation(vec, 1.0f-totalBlendAmount);
+				cur += vec;
+			}
+		}
+
+	for(node = updateData->nodes; *node; node++){
+		if((*node)->sequence){
+			bool nodelooped = (*node)->Update(vec, q, 1.0f-totalBlendAmount);
+			rot += q;
+			if((*node)->sequence->HasTranslation()){
+				pos += vec;
+				if((*node)->association->HasTranslation()){
+					trans += vec;
+					looped |= nodelooped;
+					if(nodelooped){
+						(*node)->GetEndTranslation(vec, 1.0f-totalBlendAmount);
+						end += vec;
+					}
+				}
+			}
+		}
+		++*node;
+	}
+
+	if((frame->flag & AnimBlendFrameData::IGNORE_ROTATION) == 0){
+		rot.Normalise();
+		xform->q.imag.x = rot.x;
+		xform->q.imag.y = rot.y;
+		xform->q.imag.z = rot.z;
+		xform->q.real = rot.w;
+	}
+
+	if((frame->flag & AnimBlendFrameData::IGNORE_TRANSLATION) == 0){
+		*gpAnimBlendClump->velocity = trans - cur;
+		if(looped)
+			*gpAnimBlendClump->velocity += end;
+		xform->t.x = (pos - trans).x + frame->resetPos.x;
+		xform->t.y = (pos - trans).y + frame->resetPos.y;
+		xform->t.z = (pos - trans).z + frame->resetPos.z;
+	}
+}
+
+#endif
diff --git a/src/animation/RpAnimBlend.cpp b/src/animation/RpAnimBlend.cpp
index 20290666..d3e10889 100644
--- a/src/animation/RpAnimBlend.cpp
+++ b/src/animation/RpAnimBlend.cpp
@@ -1,12 +1,17 @@
 #include "common.h"
 
+#include "RwHelper.h"
 #include "General.h"
 #include "NodeName.h"
 #include "VisibilityPlugins.h"
+#include "Bones.h"
 #include "AnimBlendClumpData.h"
 #include "AnimBlendHierarchy.h"
 #include "AnimBlendAssociation.h"
 #include "RpAnimBlend.h"
+#ifdef PED_SKIN
+#include "PedModelInfo.h"
+#endif
 
 RwInt32 ClumpOffset;
 
@@ -122,19 +127,59 @@ FrameForAllChildrenFillFrameArrayCallBack(RwFrame *frame, void *data)
 	return frame;
 }
 
+// FrameInitCallBack on PS2
 void
-FrameInitCallBack(AnimBlendFrameData *frameData, void*)
+FrameInitCBnonskin(AnimBlendFrameData *frameData, void*)
 {
 	frameData->flag = 0;
 	frameData->resetPos = *RwMatrixGetPos(RwFrameGetMatrix(frameData->frame));
 }
 
 void
-RpAnimBlendClumpInit(RpClump *clump)
+FrameInitCBskin(AnimBlendFrameData *frameData, void*)
 {
+	frameData->flag = 0;
+}
+
 #ifdef PED_SKIN
-	TODO
-#else
+void
+RpAnimBlendClumpInitSkinned(RpClump *clump)
+{
+	int i;
+	RwV3d boneTab[64];
+	CAnimBlendClumpData *clumpData;
+	RpAtomic *atomic;
+	RpSkin *skin;
+	RpHAnimHierarchy *hier;
+	int numBones;
+
+	RpAnimBlendAllocateData(clump);
+	clumpData = *RPANIMBLENDCLUMPDATA(clump);
+	atomic = IsClumpSkinned(clump);
+	assert(atomic);
+	skin = RpSkinGeometryGetSkin(RpAtomicGetGeometry(atomic));
+	assert(skin);
+	numBones = RpSkinGetNumBones(skin);
+	clumpData->SetNumberOfBones(numBones);
+	hier = GetAnimHierarchyFromSkinClump(clump);
+	assert(hier);
+	memset(boneTab, 0, sizeof(boneTab));
+	SkinGetBonePositionsToTable(clump, boneTab);
+
+	AnimBlendFrameData *frames = clumpData->frames;
+	for(i = 0; i < numBones; i++){
+		frames[i].nodeID = HIERNODEID(hier, i);
+		frames[i].resetPos = boneTab[i];
+		frames[i].hanimFrame = (RpHAnimStdKeyFrame*)rpHANIMHIERARCHYGETINTERPFRAME(hier, i);
+	}
+	clumpData->ForAllFrames(FrameInitCBskin, nil);
+	clumpData->frames[0].flag |= AnimBlendFrameData::VELOCITY_EXTRACTION;
+}
+#endif
+
+void
+RpAnimBlendClumpInitNotSkinned(RpClump *clump)
+{
 	int numFrames = 0;
 	CAnimBlendClumpData *clumpData;
 	RwFrame *root;
@@ -147,9 +192,19 @@ RpAnimBlendClumpInit(RpClump *clump)
 	clumpData->SetNumberOfFrames(numFrames);
 	frames = clumpData->frames;
 	RwFrameForAllChildren(root, FrameForAllChildrenFillFrameArrayCallBack, &frames);
-	clumpData->ForAllFrames(FrameInitCallBack, nil);
+	clumpData->ForAllFrames(FrameInitCBnonskin, nil);
 	clumpData->frames[0].flag |= AnimBlendFrameData::VELOCITY_EXTRACTION;
+}
+
+void
+RpAnimBlendClumpInit(RpClump *clump)
+{
+#ifdef PED_SKIN
+	if(IsClumpSkinned(clump))
+		RpAnimBlendClumpInitSkinned(clump);
+	else
 #endif
+		RpAnimBlendClumpInitNotSkinned(clump);
 }
 
 bool
@@ -298,42 +353,68 @@ RpAnimBlendClumpGetFirstAssociation(RpClump *clump)
 	return CAnimBlendAssociation::FromLink(clumpData->link.next); 
 }
 
+// FillFrameArrayCallBack on PS2
 void
-FillFrameArrayCallBack(AnimBlendFrameData *frame, void *arg)
+FillFrameArrayCBnonskin(AnimBlendFrameData *frame, void *arg)
 {
 	AnimBlendFrameData **frames = (AnimBlendFrameData**)arg;
 	frames[CVisibilityPlugins::GetFrameHierarchyId(frame->frame)] = frame;
 }
 
+#ifdef PED_SKIN
+void
+RpAnimBlendClumpFillFrameArraySkin(RpClump *clump, AnimBlendFrameData **frames)
+{
+	int i;
+	CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump);
+	RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(clump);
+	for(i = PED_MID; i < PED_NODE_MAX; i++)
+		frames[i] = &clumpData->frames[RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(i))];
+}
+#endif
+
 void
 RpAnimBlendClumpFillFrameArray(RpClump *clump, AnimBlendFrameData **frames)
 {
 #ifdef PED_SKIN
-	TODO
-#else
-	(*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FillFrameArrayCallBack, frames);
+	if(IsClumpSkinned(clump))
+		RpAnimBlendClumpFillFrameArraySkin(clump, frames);
+	else
 #endif
+		(*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FillFrameArrayCBnonskin, frames);
 }
 
 AnimBlendFrameData *pFrameDataFound;
 
+// FrameFindCallBack on PS2
 void
-FrameFindCallBack(AnimBlendFrameData *frame, void *arg)
+FrameFindByNameCBnonskin(AnimBlendFrameData *frame, void *arg)
 {
 	char *nodename = GetFrameNodeName(frame->frame);
 	if(!CGeneral::faststricmp(nodename, (char*)arg))
 		pFrameDataFound = frame;
 }
 
+#ifdef PED_SKIN
+void
+FrameFindByNameCBskin(AnimBlendFrameData *frame, void *arg)
+{
+	const char *name = ConvertBoneTag2BoneName(frame->nodeID);
+	if(name && CGeneral::faststricmp(name, (char*)arg) == 0)
+		pFrameDataFound = frame;
+}
+#endif
+
 AnimBlendFrameData*
 RpAnimBlendClumpFindFrame(RpClump *clump, const char *name)
 {
 	pFrameDataFound = nil;
 #ifdef PED_SKIN
-	TODO
-#else
-	(*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FrameFindCallBack, (void*)name);
+	if(IsClumpSkinned(clump))
+		(*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FrameFindByNameCBskin, (void*)name);
+	else
 #endif
+		(*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FrameFindByNameCBnonskin, (void*)name);
 	return pFrameDataFound;
 }
 
@@ -369,7 +450,12 @@ RpAnimBlendClumpUpdateAnimations(RpClump *clump, float timeDelta)
 	}
 	updateData.nodes[i] = nil;
 
-	clumpData->ForAllFrames(FrameUpdateCallBack, &updateData);
+#ifdef PED_SKIN
+	if(IsClumpSkinned(clump))
+		clumpData->ForAllFrames(FrameUpdateCallBackSkinned, &updateData);
+	else
+#endif
+		clumpData->ForAllFrames(FrameUpdateCallBackNonSkinned, &updateData);
 
 	for(link = clumpData->link.next; link; link = link->next){
 		CAnimBlendAssociation *assoc = CAnimBlendAssociation::FromLink(link);
diff --git a/src/animation/RpAnimBlend.h b/src/animation/RpAnimBlend.h
index ccfa5872..838c8816 100644
--- a/src/animation/RpAnimBlend.h
+++ b/src/animation/RpAnimBlend.h
@@ -7,7 +7,7 @@ struct AnimBlendFrameData;
 
 struct AnimBlendFrameUpdateData
 {
-	int foobar;
+	int foobar;	// TODO: figure out what this actually means
 	CAnimBlendNode *nodes[16];
 };
 
@@ -38,4 +38,5 @@ void RpAnimBlendClumpUpdateAnimations(RpClump* clump, float timeDelta);
 
 
 extern CAnimBlendClumpData *gpAnimBlendClump;
-void FrameUpdateCallBack(AnimBlendFrameData *frame, void *arg);
+void FrameUpdateCallBackNonSkinned(AnimBlendFrameData *frame, void *arg);
+void FrameUpdateCallBackSkinned(AnimBlendFrameData *frame, void *arg);
diff --git a/src/control/Script.cpp b/src/control/Script.cpp
index 073cb661..7ab40847 100644
--- a/src/control/Script.cpp
+++ b/src/control/Script.cpp
@@ -6435,9 +6435,7 @@ int8 CRunningScript::ProcessCommands800To899(int32 command)
 				pPed->FlagToDestroyWhenNextProcessed();
 		}
 		else if (CGame::nastyGame && pPed->IsPedInControl()) {
-			RwMatrix tmp_rw;
-			CPedIK::GetWorldMatrix(pPed->m_pFrames[PED_HEAD]->frame, &tmp_rw);
-			pPed->ApplyHeadShot(WEAPONTYPE_SNIPERRIFLE, tmp_rw.pos, true);
+			pPed->ApplyHeadShot(WEAPONTYPE_SNIPERRIFLE, pPed->GetNodePosition(PED_HEAD), true);
 		}
 		else {
 			pPed->SetDie(ANIM_KO_SHOT_FRONT1, 4.0f, 0.0f);
@@ -6450,9 +6448,7 @@ int8 CRunningScript::ProcessCommands800To899(int32 command)
 		CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed;
 		assert(pPed);
 		if (CGame::nastyGame) {
-			RwMatrix tmp_rw;
-			CPedIK::GetWorldMatrix(pPed->m_pFrames[PED_HEAD]->frame, &tmp_rw);
-			pPed->ApplyHeadShot(WEAPONTYPE_SNIPERRIFLE, tmp_rw.pos, true);
+			pPed->ApplyHeadShot(WEAPONTYPE_SNIPERRIFLE, pPed->GetNodePosition(PED_HEAD), true);
 		}
 		else {
 			pPed->SetDie(ANIM_KO_SHOT_FRONT1, 4.0f, 0.0f);
diff --git a/src/core/AnimViewer.cpp b/src/core/AnimViewer.cpp
index 20e94bf4..cf3ec5bf 100644
--- a/src/core/AnimViewer.cpp
+++ b/src/core/AnimViewer.cpp
@@ -367,7 +367,12 @@ CAnimViewer::Update(void)
 					} else {
 						// Originally it was GetPad(1)->LeftShoulder2
 						if (pad->NewState.Triangle) {
-							CPedModelInfo::AnimatePedColModel(((CPedModelInfo*)CModelInfo::GetModelInfo(pTarget->m_modelIndex))->GetHitColModel(), RpClumpGetFrame(pTarget->GetClump()));
+#ifdef PED_SKIN
+							if(IsClumpSkinned(pTarget->GetClump()))
+								((CPedModelInfo*)CModelInfo::GetModelInfo(pTarget->m_modelIndex))->AnimatePedColModelSkinned(pTarget->GetClump());
+							else
+#endif
+								CPedModelInfo::AnimatePedColModel(((CPedModelInfo*)CModelInfo::GetModelInfo(pTarget->m_modelIndex))->GetHitColModel(), RpClumpGetFrame(pTarget->GetClump()));
 							AsciiToUnicode("Ped Col model will be animated as long as you hold the button", gUString);
 							CMessages::AddMessage(gUString, 100, 0);
 						}
diff --git a/src/core/Cam.cpp b/src/core/Cam.cpp
index f0a60093..5cf1c8f3 100644
--- a/src/core/Cam.cpp
+++ b/src/core/Cam.cpp
@@ -2776,17 +2776,20 @@ CCam::Process_1rstPersonPedOnPC(const CVector&, float TargetOrientation, float,
 
 	if(CamTargetEntity->IsPed()){
 		// static bool FailedTestTwelveFramesAgo = false;	// unused
-		RwV3d HeadPos = vecHeadCamOffset;
+		CVector HeadPos = vecHeadCamOffset;
 		CVector TargetCoors;
 
-		// needs fix for SKINNING
-		RwFrame *frm = ((CPed*)CamTargetEntity)->GetNodeFrame(PED_HEAD);
+		((CPed*)CamTargetEntity)->TransformToNode(HeadPos, PED_HEAD);
+		// This is done on PC, but checking for the clump frame is not necessary apparently
+/*
+		RwFrame *frm = ((CPed*)CamTargetEntity)->m_pFrames[PED_HEAD]->frame;
 		while(frm){
 			RwV3dTransformPoints(&HeadPos, &HeadPos, 1, RwFrameGetMatrix(frm));
 			frm = RwFrameGetParent(frm);
 			if(frm == RpClumpGetFrame(CamTargetEntity->GetClump()))
 				frm = nil;
 		}
+*/
 
 		if(ResetStatics){
 			Beta = TargetOrientation;
@@ -2813,13 +2816,13 @@ CCam::Process_1rstPersonPedOnPC(const CVector&, float TargetOrientation, float,
 			m_vecBufferedPlayerBodyOffset.z =
 				TheCamera.m_fGaitSwayBuffer * m_vecBufferedPlayerBodyOffset.z +
 				(1.0f-TheCamera.m_fGaitSwayBuffer) * HeadPos.z;
-			HeadPos = (CamTargetEntity->GetMatrix() * m_vecBufferedPlayerBodyOffset).toRwV3d();
+			HeadPos = (CamTargetEntity->GetMatrix() * m_vecBufferedPlayerBodyOffset);
 		}else{
 			float HeadDelta = (HeadPos - InitialHeadPos).Magnitude2D();
 			CVector Fwd = CamTargetEntity->GetForward();
 			Fwd.z = 0.0f;
 			Fwd.Normalise();
-			HeadPos = (HeadDelta*1.23f*Fwd + CamTargetEntity->GetPosition()).toRwV3d();
+			HeadPos = (HeadDelta*1.23f*Fwd + CamTargetEntity->GetPosition());
 			HeadPos.z += 0.59f;
 		}
 		Source = HeadPos;
diff --git a/src/core/Debug.cpp b/src/core/Debug.cpp
index 917c99ab..e794dcaf 100644
--- a/src/core/Debug.cpp
+++ b/src/core/Debug.cpp
@@ -1,5 +1,7 @@
 #include "common.h"
+#include "RwHelper.h"
 #include "Debug.h"
+#include "Lines.h"
 #include "Font.h"
 #include "main.h"
 #include "Text.h"
@@ -114,11 +116,14 @@ CDebug::DisplayScreenStrings()
 	CFont::SetFontStyle(FONT_BANK);
 
 	for(i = 0; i < ms_nScreenStrs; i++){
+/*
 		AsciiToUnicode(ms_aScreenStrs[i].str, gUString);
 		CFont::SetColor(CRGBA(0, 0, 0, 255));
 		CFont::PrintString(ms_aScreenStrs[i].x, ms_aScreenStrs[i].y, gUString);
 		CFont::SetColor(CRGBA(255, 255, 255, 255));
 		CFont::PrintString(ms_aScreenStrs[i].x+1, ms_aScreenStrs[i].y+1, gUString);
+*/
+		ObrsPrintfString(ms_aScreenStrs[i].str, ms_aScreenStrs[i].x, ms_aScreenStrs[i].y);
 	}
 	CFont::DrawFonts();
 
@@ -131,7 +136,35 @@ CDebug::PrintAt(const char *str, int x, int y)
 	if(ms_nScreenStrs >= MAX_SCREEN_STRS)
 		return;
 	strncpy(ms_aScreenStrs[ms_nScreenStrs].str, str, 256);
-	ms_aScreenStrs[ms_nScreenStrs].x = x*12;
-	ms_aScreenStrs[ms_nScreenStrs].y = y*22;
+	ms_aScreenStrs[ms_nScreenStrs].x = x;//*12;
+	ms_aScreenStrs[ms_nScreenStrs].y = y;//*22;
 	ms_nScreenStrs++;
 }
+
+CDebug::Line CDebug::ms_aLines[MAX_DEBUG_LINES];
+int CDebug::ms_nLines;
+
+void
+CDebug::AddLine(CVector p1, CVector p2, uint32 c1, uint32 c2)
+{
+	if(ms_nLines >= MAX_DEBUG_LINES)
+		return;
+	ms_aLines[ms_nLines].p1 = p1;
+	ms_aLines[ms_nLines].p2 = p2;
+	ms_aLines[ms_nLines].c1 = c1;
+	ms_aLines[ms_nLines].c2 = c2;
+	ms_nLines++;
+}
+
+void
+CDebug::DrawLines(void)
+{
+	int i;
+	RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil);
+	RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE);
+	for(i = 0; i < ms_nLines; i++){
+		Line *l = &ms_aLines[i];
+		CLines::RenderLineWithClipping(l->p1.x, l->p1.y, l->p1.z, l->p2.x, l->p2.y, l->p2.z, l->c1, l->c2);
+	}
+	ms_nLines = 0;
+}
diff --git a/src/core/Debug.h b/src/core/Debug.h
index d169a0b4..4a25bf41 100644
--- a/src/core/Debug.h
+++ b/src/core/Debug.h
@@ -8,6 +8,7 @@ class CDebug
 		MAX_STR_LEN = 80,
 
 		MAX_SCREEN_STRS = 100,
+		MAX_DEBUG_LINES = 100,
 	};
 
 	static int16 ms_nCurrentTextLine;
@@ -21,6 +22,13 @@ class CDebug
 	static ScreenStr ms_aScreenStrs[MAX_SCREEN_STRS];
 	static int ms_nScreenStrs;
 
+	struct Line {
+		CVector p1, p2;
+		uint32 c1, c2;
+	};
+	static Line ms_aLines[MAX_DEBUG_LINES];
+	static int ms_nLines;
+
 public:
 	static void DebugInitTextBuffer();
 	static void DebugDisplayTextBuffer();
@@ -29,6 +37,9 @@ public:
 	// custom
 	static void PrintAt(const char *str, int x, int y);
 	static void DisplayScreenStrings();
+
+	static void AddLine(CVector p1, CVector p2, uint32 c1, uint32 c2);
+	static void DrawLines(void);
 };
 
 extern bool gbDebugStuffInRelease;
diff --git a/src/core/World.cpp b/src/core/World.cpp
index 404b92c0..b2a01b80 100644
--- a/src/core/World.cpp
+++ b/src/core/World.cpp
@@ -339,6 +339,11 @@ CWorld::ProcessLineOfSightSectorList(CPtrList &list, const CColLine &line, CColP
 
 			if(e->IsPed()) {
 				if(e->bUsesCollision || deadPeds && ((CPed *)e)->m_nPedState == PED_DEAD) {
+#ifdef PED_SKIN
+					if(IsClumpSkinned(e->GetClump()))
+						colmodel = ((CPedModelInfo *)CModelInfo::GetModelInfo(e->GetModelIndex()))->AnimatePedColModelSkinned(e->GetClump());
+					else
+#endif
 					if(((CPed *)e)->UseGroundColModel())
 						colmodel = &CTempColModels::ms_colModelPedGroundHit;
 					else
diff --git a/src/core/common.h b/src/core/common.h
index 46b4d03a..8b057efa 100644
--- a/src/core/common.h
+++ b/src/core/common.h
@@ -27,9 +27,15 @@
 #ifdef LIBRW
 #define STREAMPOS(str) ((str)->tell())
 #define STREAMFILE(str) (((rw::StreamFile*)(str))->file)
+#define HIERNODEINFO(hier) ((hier)->nodeInfo)
+#define HIERNODEID(hier, i) ((hier)->nodeInfo[i].id)
+#define HANIMFRAMES(anim) ((anim)->keyframes)
 #else
 #define STREAMPOS(str) ((str)->Type.memory.position)
 #define STREAMFILE(str) ((str)->Type.file.fpFile)
+#define HIERNODEINFO(hier) ((hier)->pNodeInfo)
+#define HIERNODEID(hier, i) ((hier)->pNodeInfo[i].nodeID)
+#define HANIMFRAMES(anim) ((anim)->pFrames)
 #endif
 
 #define rwVENDORID_ROCKSTAR 0x0253F2
@@ -63,6 +69,11 @@ typedef uint16_t wchar;
 
 #include "config.h"
 
+#ifdef PED_SKIN
+#include <rphanim.h>
+#include <rpskin.h>
+#endif
+
 #define ALIGNPTR(p) (void*)((((uintptr)(void*)p) + sizeof(void*)-1) & ~(sizeof(void*)-1))
 
 // PDP-10 like byte functions
diff --git a/src/core/config.h b/src/core/config.h
index c52a708d..84712af3 100644
--- a/src/core/config.h
+++ b/src/core/config.h
@@ -234,6 +234,7 @@ enum Config {
 #define CAMERA_PICKUP
 
 // Peds
+#define PED_SKIN		// support for skinned geometry on peds
 #define ANIMATE_PED_COL_MODEL
 #define VC_PED_PORTS			// various ports from VC's CPed, mostly subtle
 // #define NEW_WALK_AROUND_ALGORITHM	// to make walking around vehicles/objects less awkward
diff --git a/src/core/main.cpp b/src/core/main.cpp
index 77daaa01..7013b10f 100644
--- a/src/core/main.cpp
+++ b/src/core/main.cpp
@@ -794,6 +794,7 @@ RenderDebugShit(void)
 	if(gbShowCollisionLines)
 		CRenderer::RenderCollisionLines();
 	ThePaths.DisplayPathData();
+	CDebug::DrawLines();
 #endif
 }
 
diff --git a/src/entities/Entity.cpp b/src/entities/Entity.cpp
index 3dce53da..756e1232 100644
--- a/src/entities/Entity.cpp
+++ b/src/entities/Entity.cpp
@@ -1,6 +1,7 @@
 #include "common.h"
 
 #include "General.h"
+#include "RwHelper.h"
 #include "ModelIndices.h"
 #include "Timer.h"
 #include "Placeable.h"
@@ -24,6 +25,8 @@
 #include "References.h"
 #include "TxdStore.h"
 #include "Zones.h"
+#include "Bones.h"
+#include "Debug.h"
 
 int gBuildings;
 
@@ -282,6 +285,28 @@ CEntity::CreateRwObject(void)
 	}
 }
 
+#ifdef PED_SKIN
+RpAtomic*
+AtomicRemoveAnimFromSkinCB(RpAtomic *atomic, void *data)
+{
+	if(RpSkinGeometryGetSkin(RpAtomicGetGeometry(atomic))){
+		RpHAnimHierarchy *hier = RpSkinAtomicGetHAnimHierarchy(atomic);
+#ifdef LIBRW
+		if(hier && hier->interpolator->currentAnim){
+			RpHAnimAnimationDestroy(hier->interpolator->currentAnim);
+			hier->interpolator->currentAnim = nil;
+		}
+#else
+		if(hier && hier->pCurrentAnim){
+			RpHAnimAnimationDestroy(hier->pCurrentAnim);
+			hier->pCurrentAnim = nil;
+		}
+#endif
+	}
+	return atomic;
+}
+#endif
+
 void
 CEntity::DeleteRwObject(void)
 {
@@ -293,8 +318,13 @@ CEntity::DeleteRwObject(void)
 			f = RpAtomicGetFrame((RpAtomic*)m_rwObject);
 			RpAtomicDestroy((RpAtomic*)m_rwObject);
 			RwFrameDestroy(f);
-		}else if(RwObjectGetType(m_rwObject) == rpCLUMP)
+		}else if(RwObjectGetType(m_rwObject) == rpCLUMP){
+#ifdef PED_SKIN
+			if(IsClumpSkinned((RpClump*)m_rwObject))
+				RpClumpForAllAtomics((RpClump*)m_rwObject, AtomicRemoveAnimFromSkinCB, nil);
+#endif
 			RpClumpDestroy((RpClump*)m_rwObject);
+		}
 		m_rwObject = nil;
 		CModelInfo::GetModelInfo(m_modelIndex)->RemoveRef();
 		if(IsBuilding())
@@ -558,6 +588,44 @@ CEntity::PruneReferences(void)
 	}
 }
 
+#ifdef PED_SKIN
+void
+CEntity::UpdateRpHAnim(void)
+{
+	RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump());
+	RpHAnimHierarchyUpdateMatrices(hier);
+
+#if 0
+	int i;
+	char buf[256];
+	if(this == (CEntity*)FindPlayerPed())
+	for(i = 0; i < hier->numNodes; i++){
+		RpHAnimStdKeyFrame *kf = (RpHAnimStdKeyFrame*)rpHANIMHIERARCHYGETINTERPFRAME(hier, i);
+		sprintf(buf, "%6.3f %6.3f %6.3f %6.3f  %6.3f %6.3f %6.3f  %d %s",
+			kf->q.imag.x, kf->q.imag.y, kf->q.imag.z, kf->q.real,
+			kf->t.x, kf->t.y, kf->t.z,
+			HIERNODEID(hier, i),
+			ConvertBoneTag2BoneName(HIERNODEID(hier, i)));
+		CDebug::PrintAt(buf, 10, 1+i*3);
+
+		RwMatrix *m = &RpHAnimHierarchyGetMatrixArray(hier)[i];
+		sprintf(buf, "%6.3f %6.3f %6.3f %6.3f",
+			m->right.x, m->up.x, m->at.x, m->pos.x);
+		CDebug::PrintAt(buf, 80, 1+i*3+0);
+		sprintf(buf, "%6.3f %6.3f %6.3f %6.3f",
+			m->right.y, m->up.y, m->at.y, m->pos.y);
+		CDebug::PrintAt(buf, 80, 1+i*3+1);
+		sprintf(buf, "%6.3f %6.3f %6.3f %6.3f",
+			m->right.z, m->up.z, m->at.z, m->pos.z);
+		CDebug::PrintAt(buf, 80, 1+i*3+2);
+	}
+
+	void RenderSkeleton(RpHAnimHierarchy *hier);
+	RenderSkeleton(hier);
+#endif
+}
+#endif
+
 void
 CEntity::AddSteamsFromGround(CVector *unused)
 {
diff --git a/src/entities/Entity.h b/src/entities/Entity.h
index 8c2634f3..ee9e6490 100644
--- a/src/entities/Entity.h
+++ b/src/entities/Entity.h
@@ -148,6 +148,10 @@ public:
 	void ResolveReferences(void);
 	void PruneReferences(void);
 
+#ifdef PED_SKIN
+	void UpdateRpHAnim(void);
+#endif
+
 	void PreRenderForGlassWindow(void);
 	void AddSteamsFromGround(CVector *unused);
 	void ModifyMatrixForTreeInWind(void);
diff --git a/src/fakerw/fake.cpp b/src/fakerw/fake.cpp
index 3977007f..59c01c91 100644
--- a/src/fakerw/fake.cpp
+++ b/src/fakerw/fake.cpp
@@ -4,14 +4,10 @@
 #include <rpworld.h>
 #include <rpmatfx.h>
 #include <rphanim.h>
+#include <rpskin.h>
 #include <assert.h>
 #include <string.h>
 
-// TODO: split image<->raster functions in two
-//       implement raster context
-//	BMP reader
-//	geometry locking
-
 using namespace rw;
 
 RwUInt8 RwObjectGetType(const RwObject *obj) { return obj->type; }
@@ -55,7 +51,7 @@ RwMatrix *RwMatrixMultiply(RwMatrix * matrixOut, const RwMatrix * MatrixIn1, con
 RwMatrix *RwMatrixTransform(RwMatrix * matrix, const RwMatrix * transform, RwOpCombineType combineOp)
 	{ matrix->transform(transform, (rw::CombineOp)combineOp); return matrix; }
 //RwMatrix *RwMatrixOrthoNormalize(RwMatrix * matrixOut, const RwMatrix * matrixIn);
-//RwMatrix *RwMatrixInvert(RwMatrix * matrixOut, const RwMatrix * matrixIn);
+RwMatrix *RwMatrixInvert(RwMatrix * matrixOut, const RwMatrix * matrixIn) { Matrix::invert(matrixOut, matrixIn); return matrixOut; }
 RwMatrix *RwMatrixScale(RwMatrix * matrix, const RwV3d * scale, RwOpCombineType combineOp)
 	{ matrix->scale(scale, (rw::CombineOp)combineOp); return matrix; }
 RwMatrix *RwMatrixTranslate(RwMatrix * matrix, const RwV3d * translation, RwOpCombineType combineOp)
@@ -742,14 +738,24 @@ RwBool RpHAnimPluginAttach(void) {
 	return true;
 }
 
+RwInt32 RpHAnimFrameGetID(RwFrame *frame) { return HAnimData::get(frame)->id; }
+
+RwInt32 RpHAnimIDGetIndex(RpHAnimHierarchy *hierarchy, RwInt32 ID) { return hierarchy->getIndex(ID); }
+
 RwBool RpHAnimFrameSetHierarchy(RwFrame *frame, RpHAnimHierarchy *hierarchy) { HAnimData::get(frame)->hierarchy = hierarchy; return true; }
 RpHAnimHierarchy *RpHAnimFrameGetHierarchy(RwFrame *frame) { return HAnimHierarchy::get(frame); }
 
-RwBool RpHAnimHierarchySetCurrentAnim(RpHAnimHierarchy *hierarchy, RpHAnimAnimation *anim) { hierarchy->currentAnim->setCurrentAnim(anim); return true; }
-RwBool RpHAnimHierarchyAddAnimTime(RpHAnimHierarchy *hierarchy, RwReal time) { hierarchy->currentAnim->addTime(time); return true; }
+RpHAnimHierarchy *RpHAnimHierarchySetFlags(RpHAnimHierarchy *hierarchy, RpHAnimHierarchyFlag flags) { hierarchy->flags = flags; return hierarchy; }
 
+RwBool RpHAnimHierarchySetCurrentAnim(RpHAnimHierarchy *hierarchy, RpHAnimAnimation *anim) { hierarchy->interpolator->setCurrentAnim(anim); return true; }
+RwBool RpHAnimHierarchyAddAnimTime(RpHAnimHierarchy *hierarchy, RwReal time) { hierarchy->interpolator->addTime(time); return true; }
+
+RwMatrix *RpHAnimHierarchyGetMatrixArray(RpHAnimHierarchy *hierarchy) { return hierarchy->matrices; }
 RwBool RpHAnimHierarchyUpdateMatrices(RpHAnimHierarchy *hierarchy) { hierarchy->updateMatrices(); return true; }
 
+RpHAnimAnimation *RpHAnimAnimationCreate(RwInt32 typeID, RwInt32 numFrames, RwInt32 flags, RwReal duration)
+	{ return Animation::create(AnimInterpolatorInfo::find(typeID), numFrames, flags, duration); }
+RpHAnimAnimation  *RpHAnimAnimationDestroy(RpHAnimAnimation *animation) { animation->destroy(); return animation; }
 RpHAnimAnimation  *RpHAnimAnimationStreamRead(RwStream *stream) { return Animation::streamRead(stream); }
 
 
@@ -762,6 +768,13 @@ RwBool RpSkinPluginAttach(void) {
 	return true;
 }
 
+RwUInt32 RpSkinGetNumBones( RpSkin *skin ) { return skin->numBones; }
+const RwMatrixWeights *RpSkinGetVertexBoneWeights( RpSkin *skin ) { return (RwMatrixWeights*)skin->weights; }
+const RwUInt32 *RpSkinGetVertexBoneIndices( RpSkin *skin ) { return (RwUInt32*)skin->indices; }
+const RwMatrix *RpSkinGetSkinToBoneMatrices( RpSkin *skin ) { return (const RwMatrix*)skin->inverseMatrices; }
+
+RpSkin *RpSkinGeometryGetSkin( RpGeometry *geometry ) { return Skin::get(geometry); }
+
 RpAtomic *RpSkinAtomicSetHAnimHierarchy( RpAtomic *atomic, RpHAnimHierarchy *hierarchy ) { Skin::setHierarchy(atomic, hierarchy); return atomic; }
 RpHAnimHierarchy *RpSkinAtomicGetHAnimHierarchy( const RpAtomic *atomic ) { return Skin::getHierarchy(atomic); }
 
@@ -772,6 +785,23 @@ RpHAnimHierarchy *RpSkinAtomicGetHAnimHierarchy( const RpAtomic *atomic ) { retu
 RwImage *RtBMPImageWrite(RwImage * image, const RwChar * imageName) { rw::writeBMP(image, imageName); return image; }
 RwImage *RtBMPImageRead(const RwChar * imageName) { return rw::readBMP(imageName); }
 
+#include "rtquat.h"
+
+RtQuat *RtQuatRotate(RtQuat * quat, const RwV3d * axis, RwReal angle, RwOpCombineType combineOp) { return quat->rotate(axis, angle/180.0f*3.14159f, (CombineOp)combineOp); }
+void RtQuatConvertToMatrix(const RtQuat * const qpQuat, RwMatrix * const mpMatrix) { mpMatrix->rotate(*qpQuat, COMBINEREPLACE); }
+
+
+#include "rtcharse.h"
+
+RwBool       RtCharsetOpen(void) { return Charset::open(); }
+void         RtCharsetClose(void) { return Charset::close(); }
+RtCharset   *RtCharsetPrint(RtCharset * charSet, const RwChar * string, RwInt32 x, RwInt32 y) { charSet->print(string, x, y, true); return charSet; }
+RtCharset   *RtCharsetPrintBuffered(RtCharset * charSet, const RwChar * string, RwInt32 x, RwInt32 y, RwBool hideSpaces) { charSet->printBuffered(string, x, y, hideSpaces); return charSet; }
+RwBool       RtCharsetBufferFlush(void) { Charset::flushBuffer(); return true; }
+RtCharset   *RtCharsetSetColors(RtCharset * charSet, const RwRGBA * foreGround, const RwRGBA * backGround) { return charSet->setColors(foreGround, backGround); }
+RtCharset   *RtCharsetGetDesc(RtCharset * charset, RtCharsetDesc * desc) { *desc = charset->desc; return charset; }
+RtCharset   *RtCharsetCreate(const RwRGBA * foreGround, const RwRGBA * backGround) { return Charset::create(foreGround, backGround); }
+RwBool       RtCharsetDestroy(RtCharset * charSet) { charSet->destroy(); return true; }
 
 
 
diff --git a/src/fakerw/rphanim.h b/src/fakerw/rphanim.h
index 665e03f8..34dfeb09 100644
--- a/src/fakerw/rphanim.h
+++ b/src/fakerw/rphanim.h
@@ -1,20 +1,56 @@
 #pragma once
 
+#include "rtquat.h"
+
 //struct RpHAnimHierarchy;
 typedef rw::HAnimHierarchy RpHAnimHierarchy;
 //struct RpHAnimAnimation;
 typedef rw::Animation RpHAnimAnimation;
 
+#define rpHANIMSTDKEYFRAMETYPEID 0x1
+
+typedef rw::HAnimKeyFrame RpHAnimStdKeyFrame;
+
+enum RpHAnimHierarchyFlag
+{
+	rpHANIMHIERARCHYSUBHIERARCHY =              rw::HAnimHierarchy::SUBHIERARCHY,
+	rpHANIMHIERARCHYNOMATRICES =                rw::HAnimHierarchy::NOMATRICES,
+
+	rpHANIMHIERARCHYUPDATEMODELLINGMATRICES = rw::HAnimHierarchy::UPDATEMODELLINGMATRICES,
+	rpHANIMHIERARCHYUPDATELTMS =              rw::HAnimHierarchy::UPDATELTMS,
+	rpHANIMHIERARCHYLOCALSPACEMATRICES =      rw::HAnimHierarchy::LOCALSPACEMATRICES
+};
+
+#define rpHANIMPOPPARENTMATRIX      rw::HAnimHierarchy::POP
+#define rpHANIMPUSHPARENTMATRIX     rw::HAnimHierarchy::PUSH
+
 RwBool RpHAnimPluginAttach(void);
 
+RwBool RpHAnimFrameSetID(RwFrame *frame, RwInt32 id);
+RwInt32 RpHAnimFrameGetID(RwFrame *frame);
+
+RwInt32 RpHAnimIDGetIndex(RpHAnimHierarchy *hierarchy, RwInt32 ID);
+
 RwBool RpHAnimFrameSetHierarchy(RwFrame *frame, RpHAnimHierarchy *hierarchy);
 RpHAnimHierarchy *RpHAnimFrameGetHierarchy(RwFrame *frame);
 
+RpHAnimHierarchy *RpHAnimHierarchySetFlags(RpHAnimHierarchy *hierarchy, RpHAnimHierarchyFlag flags);
+RpHAnimHierarchyFlag RpHAnimHierarchyGetFlags(RpHAnimHierarchy *hierarchy);
+
 RwBool RpHAnimHierarchySetCurrentAnim(RpHAnimHierarchy *hierarchy, RpHAnimAnimation *anim);
 RwBool RpHAnimHierarchySetCurrentAnimTime(RpHAnimHierarchy *hierarchy, RwReal time);
 RwBool RpHAnimHierarchySubAnimTime(RpHAnimHierarchy *hierarchy, RwReal time);
 RwBool RpHAnimHierarchyAddAnimTime(RpHAnimHierarchy *hierarchy, RwReal time);
 
+RwMatrix *RpHAnimHierarchyGetMatrixArray(RpHAnimHierarchy *hierarchy);
 RwBool RpHAnimHierarchyUpdateMatrices(RpHAnimHierarchy *hierarchy);
 
+#define rpHANIMHIERARCHYGETINTERPFRAME( hierarchy, nodeIndex )    \
+        ( (void *)( ( (RwUInt8 *)&(hierarchy->interpolator[1]) +                \
+                      ((nodeIndex) *                               \
+                       hierarchy->interpolator->currentAnimKeyFrameSize) ) ) )
+
+
+RpHAnimAnimation *RpHAnimAnimationCreate(RwInt32 typeID, RwInt32 numFrames, RwInt32 flags, RwReal duration);
+RpHAnimAnimation  *RpHAnimAnimationDestroy(RpHAnimAnimation *animation);
 RpHAnimAnimation  *RpHAnimAnimationStreamRead(RwStream *stream);
diff --git a/src/fakerw/rpskin.h b/src/fakerw/rpskin.h
index dd8551ae..1ffc9f27 100644
--- a/src/fakerw/rpskin.h
+++ b/src/fakerw/rpskin.h
@@ -2,7 +2,25 @@
 
 #include <rphanim.h>
 
+//struct RpSkin;
+typedef rw::Skin RpSkin;
+
+struct RwMatrixWeights
+{
+	RwReal w0;
+	RwReal w1;
+	RwReal w2;
+	RwReal w3;
+};
+
 RwBool RpSkinPluginAttach(void);
 
+RwUInt32 RpSkinGetNumBones( RpSkin *skin );
+const RwMatrixWeights *RpSkinGetVertexBoneWeights( RpSkin *skin );
+const RwUInt32 *RpSkinGetVertexBoneIndices( RpSkin *skin );
+const RwMatrix *RpSkinGetSkinToBoneMatrices( RpSkin *skin );
+
+RpSkin *RpSkinGeometryGetSkin( RpGeometry *geometry );
+
 RpAtomic *RpSkinAtomicSetHAnimHierarchy( RpAtomic *atomic, RpHAnimHierarchy *hierarchy );
 RpHAnimHierarchy *RpSkinAtomicGetHAnimHierarchy( const RpAtomic *atomic );
diff --git a/src/fakerw/rtcharse.h b/src/fakerw/rtcharse.h
new file mode 100644
index 00000000..10eb1f32
--- /dev/null
+++ b/src/fakerw/rtcharse.h
@@ -0,0 +1,14 @@
+#pragma once
+
+typedef rw::Charset RtCharset;
+typedef rw::Charset::Desc RtCharsetDesc;
+
+RwBool       RtCharsetOpen(void);
+void         RtCharsetClose(void);
+RtCharset   *RtCharsetPrint(RtCharset * charSet, const RwChar * string, RwInt32 x, RwInt32 y);
+RtCharset   *RtCharsetPrintBuffered(RtCharset * charSet, const RwChar * string, RwInt32 x, RwInt32 y, RwBool hideSpaces);
+RwBool       RtCharsetBufferFlush(void);
+RtCharset   *RtCharsetSetColors(RtCharset * charSet, const RwRGBA * foreGround, const RwRGBA * backGround);
+RtCharset   *RtCharsetGetDesc(RtCharset * charset, RtCharsetDesc * desc);
+RtCharset   *RtCharsetCreate(const RwRGBA * foreGround, const RwRGBA * backGround);
+RwBool       RtCharsetDestroy(RtCharset * charSet);
diff --git a/src/fakerw/rtquat.h b/src/fakerw/rtquat.h
new file mode 100644
index 00000000..3cf15f5a
--- /dev/null
+++ b/src/fakerw/rtquat.h
@@ -0,0 +1,10 @@
+#pragma once
+
+typedef rw::Quat RtQuat;
+
+RwBool RtQuatConvertFromMatrix(RtQuat * const qpQuat, const RwMatrix * const mpMatrix);
+RtQuat *RtQuatRotate(RtQuat * quat, const RwV3d * axis, RwReal angle, RwOpCombineType combineOp);
+const RtQuat *RtQuatQueryRotate(const RtQuat *quat, RwV3d * unitAxis, RwReal * angle);
+RwV3d *RtQuatTransformVectors(RwV3d * vectorsOut, const RwV3d * vectorsIn, const RwInt32 numPoints, const RtQuat *quat);
+
+void RtQuatConvertToMatrix(const RtQuat * const qpQuat, RwMatrix * const mpMatrix);
diff --git a/src/math/Quaternion.h b/src/math/Quaternion.h
index fb37dc10..1d04bdff 100644
--- a/src/math/Quaternion.h
+++ b/src/math/Quaternion.h
@@ -10,6 +10,18 @@ public:
 
 	float Magnitude(void) const { return Sqrt(x*x + y*y + z*z + w*w); }
 	float MagnitudeSqr(void) const { return x*x + y*y + z*z + w*w; }
+	void Normalise(void) {
+		float sq = MagnitudeSqr();
+		if(sq == 0.0f)
+			w = 1.0f;
+		else{
+			float invsqrt = RecipSqrt(sq);
+			x *= invsqrt;
+			y *= invsqrt;
+			z *= invsqrt;
+			w *= invsqrt;
+		}
+	}
 
 	const CQuaternion &operator+=(CQuaternion const &right) {
 		x += right.x;
diff --git a/src/modelinfo/ClumpModelInfo.cpp b/src/modelinfo/ClumpModelInfo.cpp
index 464bda61..44faf3c5 100644
--- a/src/modelinfo/ClumpModelInfo.cpp
+++ b/src/modelinfo/ClumpModelInfo.cpp
@@ -1,5 +1,6 @@
 #include "common.h"
 
+#include "RwHelper.h"
 #include "General.h"
 #include "NodeName.h"
 #include "VisibilityPlugins.h"
@@ -15,12 +16,40 @@ CClumpModelInfo::DeleteRwObject(void)
 	}
 }
 
+#ifdef PED_SKIN
+static RpAtomic*
+SetHierarchyForSkinAtomic(RpAtomic *atomic, void *data)
+{
+	RpSkinAtomicSetHAnimHierarchy(atomic, (RpHAnimHierarchy*)data);
+	return nil;
+}
+#endif
+
 RwObject*
 CClumpModelInfo::CreateInstance(void)
 {
-	if(m_clump)
-		return (RwObject*)RpClumpClone(m_clump);
-	return nil;
+	if(m_clump == nil)
+		return nil;
+	RpClump *clone = RpClumpClone(m_clump);
+#ifdef PED_SKIN
+	if(IsClumpSkinned(clone)){
+		RpHAnimHierarchy *hier;
+		RpHAnimAnimation *anim;
+
+		hier = GetAnimHierarchyFromClump(clone);
+		assert(hier);
+		// This seems dangerous as only the first atomic will get a hierarchy
+		// can we guarantee this if hands and head are also in the clump?
+		RpClumpForAllAtomics(clone, SetHierarchyForSkinAtomic, hier);
+		anim = HAnimAnimationCreateForHierarchy(hier);
+		RpHAnimHierarchySetCurrentAnim(hier, anim);
+//		RpHAnimHierarchySetFlags(hier, (RpHAnimHierarchyFlag)(rpHANIMHIERARCHYUPDATEMODELLINGMATRICES|rpHANIMHIERARCHYUPDATELTMS));
+		// the rest is xbox only:
+		// RpSkinGetNumBones(RpSkinGeometryGetSkin(RpAtomicGetGeometry(IsClumpSkinned(clone))));
+		RpHAnimHierarchyUpdateMatrices(hier);
+	}
+#endif
+	return (RwObject*)clone;
 }
 
 RwObject*
@@ -48,8 +77,45 @@ CClumpModelInfo::SetClump(RpClump *clump)
 	CVisibilityPlugins::SetClumpModelInfo(m_clump, this);
 	AddTexDictionaryRef();
 	RpClumpForAllAtomics(clump, SetAtomicRendererCB, nil);
+
+	// TODO: also set for player?
 	if(strncmp(GetName(), "playerh", 8) == 0)
 		RpClumpForAllAtomics(clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPlayerCB);
+
+#ifdef PED_SKIN
+	if(IsClumpSkinned(clump)){
+		int i;
+		RpHAnimHierarchy *hier;
+		RpAtomic *skinAtomic;
+		RpSkin *skin;
+
+		// mobile:
+//		hier = nil;
+//		RwFrameForAllChildren(RpClumpGetFrame(clump), GetHierarchyFromChildNodesCB, &hier);
+//		assert(hier);
+//		RpClumpForAllAtomics(clump, SetHierarchyForSkinAtomic, hier);
+//		skinAtomic = GetFirstAtomic(clump);
+
+		// xbox:
+		hier = GetAnimHierarchyFromClump(clump);
+		assert(hier);
+		RpSkinAtomicSetHAnimHierarchy(IsClumpSkinned(clump), hier);
+		skinAtomic = IsClumpSkinned(clump);
+
+		assert(skinAtomic);
+		skin = RpSkinGeometryGetSkin(RpAtomicGetGeometry(skinAtomic));
+		// ignore const
+		for(i = 0; i < RpGeometryGetNumVertices(RpAtomicGetGeometry(skinAtomic)); i++){
+			RwMatrixWeights *weights = (RwMatrixWeights*)&RpSkinGetVertexBoneWeights(skin)[i];
+			float sum = weights->w0 + weights->w1 + weights->w2 + weights->w3;
+			weights->w0 /= sum;
+			weights->w1 /= sum;
+			weights->w2 /= sum;
+			weights->w3 /= sum;
+		}
+//		RpHAnimHierarchySetFlags(hier, (RpHAnimHierarchyFlag)(rpHANIMHIERARCHYUPDATEMODELLINGMATRICES|rpHANIMHIERARCHYUPDATELTMS));
+	}
+#endif
 }
 
 void
diff --git a/src/modelinfo/PedModelInfo.cpp b/src/modelinfo/PedModelInfo.cpp
index 5c801a2b..47080e23 100644
--- a/src/modelinfo/PedModelInfo.cpp
+++ b/src/modelinfo/PedModelInfo.cpp
@@ -1,6 +1,9 @@
 #include "common.h"
 
+#include "RwHelper.h"
 #include "General.h"
+#include "Bones.h"
+#include "SurfaceTable.h"
 #include "Ped.h"
 #include "NodeName.h"
 #include "VisibilityPlugins.h"
@@ -9,13 +12,31 @@
 void
 CPedModelInfo::DeleteRwObject(void)
 {
-	CClumpModelInfo::DeleteRwObject();
 	if(m_hitColModel)
 		delete m_hitColModel;
 	m_hitColModel = nil;
+#ifdef PED_SKIN
+	RwFrame *frame;
+	if(m_head){
+		frame = RpAtomicGetFrame(m_head);
+		RpAtomicDestroy(m_head);
+		RwFrameDestroy(frame);
+	}
+	if(m_lhand){
+		frame = RpAtomicGetFrame(m_lhand);
+		RpAtomicDestroy(m_lhand);
+		RwFrameDestroy(frame);
+	}
+	if(m_rhand){
+		frame = RpAtomicGetFrame(m_rhand);
+		RpAtomicDestroy(m_rhand);
+		RwFrameDestroy(frame);
+	}
+#endif
+	CClumpModelInfo::DeleteRwObject();	// PC calls this first
 }
 
-RwObjectNameIdAssocation CPedModelInfo::m_pPedIds[12] = {
+RwObjectNameIdAssocation CPedModelInfo::m_pPedIds[PED_NODE_MAX] = {
 	{ "Smid",	PED_MID, 0, },	// that is strange...
 	{ "Shead",	PED_HEAD, 0, },
 	{ "Supperarml",	PED_UPPERARML, 0, },
@@ -30,15 +51,70 @@ RwObjectNameIdAssocation CPedModelInfo::m_pPedIds[12] = {
 	{ nil,	0, 0, },
 };
 
+#ifdef PED_SKIN
+struct LimbCBarg
+{
+	CPedModelInfo *mi;
+	RpClump *clump;
+	int32 frameIDs[3];
+};
+
+RpAtomic*
+CPedModelInfo::findLimbsCb(RpAtomic *atomic, void *data)
+{
+	LimbCBarg *limbs = (LimbCBarg*)data;
+	RwFrame *frame = RpAtomicGetFrame(atomic);
+	const char *name = GetFrameNodeName(frame);
+	if(CGeneral::faststricmp(name, "Shead01") == 0){
+		limbs->frameIDs[0] = RpHAnimFrameGetID(frame);
+		limbs->mi->m_head = atomic;
+		RpClumpRemoveAtomic(limbs->clump, atomic);
+		RwFrameRemoveChild(frame);
+	}else if(CGeneral::faststricmp(name, "SLhand01") == 0){
+		limbs->frameIDs[1] = RpHAnimFrameGetID(frame);
+		limbs->mi->m_lhand = atomic;
+		RpClumpRemoveAtomic(limbs->clump, atomic);
+		RwFrameRemoveChild(frame);
+	}else if(CGeneral::faststricmp(name, "SRhand01") == 0){
+		limbs->frameIDs[2] = RpHAnimFrameGetID(frame);
+		limbs->mi->m_rhand = atomic;
+		RpClumpRemoveAtomic(limbs->clump, atomic);
+		RwFrameRemoveChild(frame);
+	}
+	return atomic;
+}
+#endif
+
 void
 CPedModelInfo::SetClump(RpClump *clump)
 {
+#ifdef PED_SKIN
+
+	// CB has to be set here before atomics are detached from clump
+	if(strncmp(GetName(), "player", 7) == 0)
+		RpClumpForAllAtomics(clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPlayerCB);
+	if(IsClumpSkinned(clump)){
+		LimbCBarg limbs = { this, clump, { 0, 0, 0 } };
+		RpClumpForAllAtomics(clump, findLimbsCb, &limbs);
+	}
+	CClumpModelInfo::SetClump(clump);
+	SetFrameIds(m_pPedIds);
+	if(m_hitColModel == nil && !IsClumpSkinned(clump))
+		CreateHitColModel();
+	// And again because CClumpModelInfo resets it
+	if(strncmp(GetName(), "player", 7) == 0)
+		RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPlayerCB);
+	else if(IsClumpSkinned(clump))
+		// skinned peds have no low detail version, so they don't have the right render Cb
+		RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPedCB);
+#else
 	CClumpModelInfo::SetClump(clump);
 	SetFrameIds(m_pPedIds);
 	if(m_hitColModel == nil)
 		CreateHitColModel();
 	if(strncmp(GetName(), "player", 7) == 0)
 		RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPlayerCB);
+#endif
 }
 
 RpAtomic*
@@ -157,7 +233,7 @@ CPedModelInfo::CreateHitColModel(void)
 		}
 		if(nodeFrame){
 			float radius = m_pColNodeInfos[i].radius;
-			if(m_pColNodeInfos[i].pieceType == 6)
+			if(m_pColNodeInfos[i].pieceType == PEDPIECE_HEAD)
 				RwFrameForAllObjects(nodeFrame, FindHeadRadiusCB, &radius);
 			RwMatrixTransform(mat, RwFrameGetMatrix(nodeFrame), rwCOMBINEREPLACE);
 			const char *name = GetFrameNodeName(nodeFrame);
@@ -172,7 +248,7 @@ CPedModelInfo::CreateHitColModel(void)
 			center.x = mat->pos.x + m_pColNodeInfos[i].x;
 			center.y = mat->pos.y + 0.0f;
 			center.z = mat->pos.z + m_pColNodeInfos[i].z;
-			spheres[i].Set(radius, center, 17, m_pColNodeInfos[i].pieceType);
+			spheres[i].Set(radius, center, SURFACE_FLESH, m_pColNodeInfos[i].pieceType);
 		}
 	}
 	RwMatrixDestroy(mat);
@@ -186,7 +262,7 @@ CPedModelInfo::CreateHitColModel(void)
 	max.x = max.y = 0.5f;
 	max.z = 1.2f;
 	colmodel->boundingBox.Set(min, max, 0, 0);
-	colmodel->level = 0;
+	colmodel->level = LEVEL_NONE;
 	m_hitColModel = colmodel;
 }
 
@@ -229,3 +305,81 @@ CPedModelInfo::AnimatePedColModel(CColModel* colmodel, RwFrame* frame)
 
 	return colmodel;
 }
+
+#ifdef PED_SKIN
+void
+CPedModelInfo::CreateHitColModelSkinned(RpClump *clump)
+{
+	CVector center;
+	RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(clump);
+	CColModel *colmodel = new CColModel;
+	CColSphere *spheres = (CColSphere*)RwMalloc(NUMPEDINFONODES*sizeof(CColSphere));
+	RwFrame *root = RpClumpGetFrame(m_clump);
+	RwMatrix *invmat = RwMatrixCreate();
+	RwMatrix *mat = RwMatrixCreate();
+	RwMatrixInvert(invmat, RwFrameGetMatrix(RpClumpGetFrame(clump)));
+
+	for(int i = 0; i < NUMPEDINFONODES; i++){
+		*mat = *invmat;
+		int id = ConvertPedNode2BoneTag(m_pColNodeInfos[i].pedNode);	// this is wrong, wtf R* ???
+		int idx = RpHAnimIDGetIndex(hier, id);
+
+		// This doesn't really work as the positions are not initialized yet
+		RwMatrixTransform(mat, &RpHAnimHierarchyGetMatrixArray(hier)[idx], rwCOMBINEPRECONCAT);
+		RwV3d pos = { 0.0f, 0.0f, 0.0f };
+		RwV3dTransformPoints(&pos, &pos, 1, mat);
+
+		center.x = pos.x + m_pColNodeInfos[i].x;
+		center.y = pos.y + 0.0f;
+		center.z = pos.z + m_pColNodeInfos[i].z;
+		spheres[i].Set(m_pColNodeInfos[i].radius, center, SURFACE_FLESH, m_pColNodeInfos[i].pieceType);
+	}
+	RwMatrixDestroy(invmat);
+	RwMatrixDestroy(mat);
+	colmodel->spheres = spheres;
+	colmodel->numSpheres = NUMPEDINFONODES;
+	center.x = center.y = center.z = 0.0f;
+	colmodel->boundingSphere.Set(2.0f, center, 0, 0);
+	CVector min, max;
+	min.x = min.y = -0.5f;
+	min.z = -1.2f;
+	max.x = max.y = 0.5f;
+	max.z = 1.2f;
+	colmodel->boundingBox.Set(min, max, 0, 0);
+	colmodel->level = LEVEL_NONE;
+	m_hitColModel = colmodel;
+}
+
+CColModel*
+CPedModelInfo::AnimatePedColModelSkinned(RpClump *clump)
+{
+	if(m_hitColModel == nil){
+		CreateHitColModelSkinned(clump);
+		return m_hitColModel;
+	}
+	RwMatrix *invmat, *mat;
+	CColSphere *spheres = m_hitColModel->spheres;
+	RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(clump);
+	invmat = RwMatrixCreate();
+	mat = RwMatrixCreate();
+	RwMatrixInvert(invmat, RwFrameGetMatrix(RpClumpGetFrame(clump)));
+
+	for(int i = 0; i < NUMPEDINFONODES; i++){
+		*mat = *invmat;
+		int id = ConvertPedNode2BoneTag(m_pColNodeInfos[i].pedNode);
+		int idx = RpHAnimIDGetIndex(hier, id);
+
+		RwMatrixTransform(mat, &RpHAnimHierarchyGetMatrixArray(hier)[idx], rwCOMBINEPRECONCAT);
+		RwV3d pos = { 0.0f, 0.0f, 0.0f };
+		RwV3dTransformPoints(&pos, &pos, 1, mat);
+
+		spheres[i].center.x = pos.x + m_pColNodeInfos[i].x;
+		spheres[i].center.y = pos.y + 0.0f;
+		spheres[i].center.z = pos.z + m_pColNodeInfos[i].z;
+	}
+	RwMatrixDestroy(invmat);
+	RwMatrixDestroy(mat);
+	return m_hitColModel;
+}
+
+#endif
diff --git a/src/modelinfo/PedModelInfo.h b/src/modelinfo/PedModelInfo.h
index 0336fa9b..a2bfd122 100644
--- a/src/modelinfo/PedModelInfo.h
+++ b/src/modelinfo/PedModelInfo.h
@@ -28,11 +28,13 @@ public:
 	ePedStats m_pedStatType;
 	uint32 m_carsCanDrive;
 	CColModel *m_hitColModel;
+#ifdef PED_SKIN
 	RpAtomic *m_head;
 	RpAtomic *m_lhand;
 	RpAtomic *m_rhand;
+#endif
 
-	static RwObjectNameIdAssocation m_pPedIds[12];
+	static RwObjectNameIdAssocation m_pPedIds[PED_NODE_MAX];
 
 	CPedModelInfo(void) : CClumpModelInfo(MITYPE_PED) { }
 	void DeleteRwObject(void);
@@ -40,7 +42,18 @@ public:
 
 	void SetLowDetailClump(RpClump*);
 	void CreateHitColModel(void);
+	void CreateHitColModelSkinned(RpClump *clump);
 	CColModel *GetHitColModel(void) { return m_hitColModel; }
 	static CColModel *AnimatePedColModel(CColModel* colmodel, RwFrame* frame);
+	CColModel *AnimatePedColModelSkinned(RpClump *clump);
+
+#ifdef PED_SKIN
+	static RpAtomic *findLimbsCb(RpAtomic *atomic, void *data);
+	RpAtomic *getHead(void) { return m_head; }
+	RpAtomic *getLeftHand(void) { return m_lhand; }
+	RpAtomic *getRightHand(void) { return m_rhand; }
+#endif
 };
-static_assert(sizeof(CPedModelInfo) == 0x54, "CPedModelInfo: error");
+#ifndef PED_SKIN
+static_assert(sizeof(CPedModelInfo) == 0x48, "CPedModelInfo: error");
+#endif
\ No newline at end of file
diff --git a/src/objects/CutsceneHead.cpp b/src/objects/CutsceneHead.cpp
index b716e17e..3ef257d2 100644
--- a/src/objects/CutsceneHead.cpp
+++ b/src/objects/CutsceneHead.cpp
@@ -5,6 +5,7 @@
 #include "RwHelper.h"
 #include "RpAnimBlend.h"
 #include "AnimBlendClumpData.h"
+#include "Bones.h"
 #include "Directory.h"
 #include "CutsceneMgr.h"
 #include "Streaming.h"
@@ -17,11 +18,23 @@ CCutsceneHead::CCutsceneHead(CObject *obj)
 	RpAtomic *atm;
 
 	assert(RwObjectGetType(obj->m_rwObject) == rpCLUMP);
-	m_pHeadNode = RpAnimBlendClumpFindFrame((RpClump*)obj->m_rwObject, "Shead")->frame;
-	atm = (RpAtomic*)GetFirstObject(m_pHeadNode);
-	if(atm){
-		assert(RwObjectGetType((RwObject*)atm) == rpATOMIC);
-		RpAtomicSetFlags(atm, RpAtomicGetFlags(atm) & ~rpATOMICRENDER);
+#ifdef PED_SKIN
+	unk1 = 0;
+	bIsSkinned = false;
+	m_parentObject = (CCutsceneObject*)obj;
+	// Hide original head
+	if(IsClumpSkinned(obj->GetClump())){
+		m_parentObject->SetRenderHead(false);
+		bIsSkinned = true;
+	}else
+#endif
+	{
+		m_pHeadNode = RpAnimBlendClumpFindFrame((RpClump*)obj->m_rwObject, "Shead")->frame;
+		atm = (RpAtomic*)GetFirstObject(m_pHeadNode);
+		if(atm){
+			assert(RwObjectGetType((RwObject*)atm) == rpATOMIC);
+			RpAtomicSetFlags(atm, RpAtomicGetFlags(atm) & ~rpATOMICRENDER);
+		}
 	}
 }
 
@@ -48,11 +61,28 @@ CCutsceneHead::ProcessControl(void)
 	RpAtomic *atm;
 	RpHAnimHierarchy *hier;
 
+	// android/xbox calls is at the end
 	CPhysical::ProcessControl();
 
-	m_matrix.SetRotateY(PI/2);
-	m_matrix = CMatrix(RwFrameGetLTM(m_pHeadNode)) * m_matrix;
-	UpdateRwFrame();
+#ifdef PED_SKIN
+	if(bIsSkinned){
+		UpdateRpHAnim();
+		UpdateRwFrame();
+
+		RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(m_parentObject->GetClump());
+		int idx = RpHAnimIDGetIndex(hier, BONE_head);
+		RwMatrix *mat = &RpHAnimHierarchyGetMatrixArray(hier)[idx];
+		if(RwV3dLength(&mat->pos) > 100.0f){
+			m_matrix.SetRotateY(PI/2);
+			m_matrix = CMatrix(mat) * m_matrix;
+		}
+	}else
+#endif
+	{
+		m_matrix.SetRotateY(PI/2);
+		m_matrix = CMatrix(RwFrameGetLTM(m_pHeadNode)) * m_matrix;
+		UpdateRwFrame();	// android/xbox don't call this
+	}
 
 	assert(RwObjectGetType(m_rwObject) == rpCLUMP);
 	atm = GetFirstAtomic((RpClump*)m_rwObject);
@@ -65,8 +95,25 @@ CCutsceneHead::Render(void)
 {
 	RpAtomic *atm;
 
-	m_matrix.SetRotateY(PI/2);
-	m_matrix = CMatrix(RwFrameGetLTM(m_pHeadNode)) * m_matrix;
+#ifdef PED_SKIN
+	if(bIsSkinned){
+		RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(m_parentObject->GetClump());
+		RpHAnimHierarchyUpdateMatrices(hier);
+		int idx = RpHAnimIDGetIndex(hier, BONE_head);
+		RwMatrix *mat = &RpHAnimHierarchyGetMatrixArray(hier)[idx];
+		if(RwV3dLength(&mat->pos) > 100.0f){
+			m_matrix.SetRotateY(PI/2);
+			m_matrix = CMatrix(mat) * m_matrix;
+		}
+		RenderLimb(BONE_Lhand);
+		RenderLimb(BONE_Rhand);
+	}else
+#endif
+	{
+		m_matrix.SetRotateY(PI/2);
+		m_matrix = CMatrix(RwFrameGetLTM(m_pHeadNode)) * m_matrix;
+	}
+
 	UpdateRwFrame();
 
 	assert(RwObjectGetType(m_rwObject) == rpCLUMP);
@@ -76,6 +123,34 @@ CCutsceneHead::Render(void)
 	CObject::Render();
 }
 
+#ifdef PED_SKIN
+void
+CCutsceneHead::RenderLimb(int32 bone)
+{
+	RpAtomic *atomic;
+	RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(m_parentObject->GetClump());
+	int idx = RpHAnimIDGetIndex(hier, bone);
+	RwMatrix *mats = RpHAnimHierarchyGetMatrixArray(hier);
+	CPedModelInfo *mi = (CPedModelInfo*)CModelInfo::GetModelInfo(m_modelIndex);
+	switch(bone){
+	case BONE_Lhand:
+		atomic = mi->getLeftHand();
+		break;
+	case BONE_Rhand:
+		atomic = mi->getRightHand();
+		break;
+	default:
+		return;
+	}
+	if(atomic){
+		RwFrame *frame = RpAtomicGetFrame(atomic);
+		RwMatrixTransform(RwFrameGetMatrix(frame), &mats[idx], rwCOMBINEREPLACE);
+		RwFrameUpdateObjects(frame);
+		RpAtomicRender(atomic);
+	}
+}
+#endif
+
 void
 CCutsceneHead::PlayAnimation(const char *animName)
 {
diff --git a/src/objects/CutsceneHead.h b/src/objects/CutsceneHead.h
index 52b66ede..0a70353d 100644
--- a/src/objects/CutsceneHead.h
+++ b/src/objects/CutsceneHead.h
@@ -6,6 +6,12 @@ class CCutsceneHead : public CCutsceneObject
 {
 public:
 	RwFrame *m_pHeadNode;
+#ifdef PED_SKIN
+	int32 unk1;
+	CCutsceneObject *m_parentObject;
+	int32 unk2;
+	int32 bIsSkinned;
+#endif
 
 	CCutsceneHead(CObject *obj);
 
@@ -13,7 +19,10 @@ public:
 	void DeleteRwObject(void);
 	void ProcessControl(void);
 	void Render(void);
+	void RenderLimb(int32 bone);
 
 	void PlayAnimation(const char *animName);
 };
+#ifndef PED_SKIN
 static_assert(sizeof(CCutsceneHead) == 0x19C, "CCutsceneHead: error");
+#endif
diff --git a/src/objects/CutsceneObject.cpp b/src/objects/CutsceneObject.cpp
index cee83848..7b4ae02b 100644
--- a/src/objects/CutsceneObject.cpp
+++ b/src/objects/CutsceneObject.cpp
@@ -1,10 +1,12 @@
 #include "common.h"
 
 #include "main.h"
+#include "RwHelper.h"
 #include "Lights.h"
 #include "PointLights.h"
 #include "RpAnimBlend.h"
 #include "AnimBlendClumpData.h"
+#include "Bones.h"
 #include "Renderer.h"
 #include "ModelIndices.h"
 #include "Shadows.h"
@@ -19,6 +21,12 @@ CCutsceneObject::CCutsceneObject(void)
 	ObjectCreatedBy = CUTSCENE_OBJECT;
 	m_fMass = 1.0f;
 	m_fTurnMass = 1.0f;
+
+#ifdef PED_SKIN
+	bRenderHead = true;
+	bRenderRightHand = true;
+	bRenderLeftHand = true;
+#endif
 }
 
 void
@@ -42,12 +50,24 @@ CCutsceneObject::ProcessControl(void)
 		m_vecMoveSpeed *= 1.0f/CTimer::GetTimeStep();
 
 	ApplyMoveSpeed();
+
+#ifdef PED_SKIN
+	if(IsClumpSkinned(GetClump()))
+		UpdateRpHAnim();
+#endif
+}
+
+static RpMaterial*
+MaterialSetAlpha(RpMaterial *material, void *data)
+{
+	((RwRGBA*)RpMaterialGetColor(material))->alpha = (uint8)(uintptr)data;
+	return material;
 }
 
 void
 CCutsceneObject::PreRender(void)
 {
-	if(IsPedModel(GetModelIndex()))
+	if(IsPedModel(GetModelIndex())){
 		CShadows::StoreShadowForPedObject(this,
 			CTimeCycle::m_fShadowDisplacementX[CTimeCycle::m_CurrentStoredValue],
 			CTimeCycle::m_fShadowDisplacementY[CTimeCycle::m_CurrentStoredValue],
@@ -55,14 +75,57 @@ CCutsceneObject::PreRender(void)
 			CTimeCycle::m_fShadowFrontY[CTimeCycle::m_CurrentStoredValue],
 			CTimeCycle::m_fShadowSideX[CTimeCycle::m_CurrentStoredValue],
 			CTimeCycle::m_fShadowSideY[CTimeCycle::m_CurrentStoredValue]);
+		// For some reason xbox/android limbs are transparent here...
+		RpGeometry *geometry = RpAtomicGetGeometry(GetFirstAtomic(GetClump()));
+		RpGeometrySetFlags(geometry, RpGeometryGetFlags(geometry) | rpGEOMETRYMODULATEMATERIALCOLOR);
+		RpGeometryForAllMaterials(geometry, MaterialSetAlpha, (void*)255);
+	}
 }
 
 void
 CCutsceneObject::Render(void)
 {
+#ifdef PED_SKIN
+	if(IsClumpSkinned(GetClump())){
+		if(bRenderLeftHand) RenderLimb(BONE_Lhand);
+		if(bRenderRightHand) RenderLimb(BONE_Rhand);
+		if(bRenderHead) RenderLimb(BONE_head);
+	}
+#endif
 	CObject::Render();
 }
 
+#ifdef PED_SKIN
+void
+CCutsceneObject::RenderLimb(int32 bone)
+{
+	RpAtomic *atomic;
+	CPedModelInfo *mi = (CPedModelInfo*)CModelInfo::GetModelInfo(m_modelIndex);
+	switch(bone){
+	case BONE_head:
+		atomic = mi->getHead();
+		break;
+	case BONE_Lhand:
+		atomic = mi->getLeftHand();
+		break;
+	case BONE_Rhand:
+		atomic = mi->getRightHand();
+		break;
+	default:
+		return;
+	}
+	if(atomic){
+		RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump());
+		int idx = RpHAnimIDGetIndex(hier, bone);
+		RwMatrix *mat = &RpHAnimHierarchyGetMatrixArray(hier)[idx];
+		RwFrame *frame = RpAtomicGetFrame(atomic);
+		*RwFrameGetMatrix(frame) = *mat;
+		RwFrameUpdateObjects(frame);
+		RpAtomicRender(atomic);
+	}
+}
+#endif
+
 bool
 CCutsceneObject::SetupLighting(void)
 {
diff --git a/src/objects/CutsceneObject.h b/src/objects/CutsceneObject.h
index 31c3a528..9c4036bf 100644
--- a/src/objects/CutsceneObject.h
+++ b/src/objects/CutsceneObject.h
@@ -5,13 +5,29 @@
 class CCutsceneObject : public CObject
 {
 public:
+#ifdef PED_SKIN
+	bool bRenderHead;
+	bool bRenderRightHand;
+	bool bRenderLeftHand;
+
+	bool GetRenderHead(void) { return bRenderHead; }
+	bool GetRenderRightHand(void) { return bRenderRightHand; }
+	bool GetRenderLeftHand(void) { return bRenderLeftHand; }
+	void SetRenderHead(bool render) { bRenderHead = render; }
+	void SetRenderRightHand(bool render) { bRenderRightHand = render; }
+	void SetRenderLeftHand(bool render) { bRenderLeftHand = render; }
+#endif
+
 	CCutsceneObject(void);
 
 	void SetModelIndex(uint32 id);
 	void ProcessControl(void);
 	void PreRender(void);
 	void Render(void);
+	void RenderLimb(int32 bone);
 	bool SetupLighting(void);
 	void RemoveLighting(bool reset);
 };
+#ifndef PED_SKIN
 static_assert(sizeof(CCutsceneObject) == 0x198, "CCutsceneObject: error");
+#endif
diff --git a/src/peds/CivilianPed.h b/src/peds/CivilianPed.h
index 6082c6ab..88d034c8 100644
--- a/src/peds/CivilianPed.h
+++ b/src/peds/CivilianPed.h
@@ -11,4 +11,6 @@ public:
 	void CivilianAI(void);
 	void ProcessControl(void);
 };
+#ifndef PED_SKIN
 static_assert(sizeof(CCivilianPed) == 0x53C, "CCivilianPed: error");
+#endif
diff --git a/src/peds/CopPed.cpp b/src/peds/CopPed.cpp
index 98a07316..3fc8b8ca 100644
--- a/src/peds/CopPed.cpp
+++ b/src/peds/CopPed.cpp
@@ -465,8 +465,7 @@ CCopPed::CopAI(void)
 				if (m_fDistanceToTarget < weaponRange) {
 					CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType);
 					CVector gunPos = weaponInfo->m_vecFireOffset;
-					for (RwFrame *i = GetNodeFrame(PED_HANDR); i; i = RwFrameGetParent(i))
-						RwV3dTransformPoints((RwV3d*)&gunPos, (RwV3d*)&gunPos, 1, RwFrameGetMatrix(i));
+					TransformToNode(gunPos, PED_HANDR);
 
 					CColPoint foundCol;
 					CEntity *foundEnt;
diff --git a/src/peds/CopPed.h b/src/peds/CopPed.h
index e3454875..e9780035 100644
--- a/src/peds/CopPed.h
+++ b/src/peds/CopPed.h
@@ -36,4 +36,6 @@ public:
 	void CopAI(void);
 };
 
+#ifndef PED_SKIN
 static_assert(sizeof(CCopPed) == 0x558, "CCopPed: error");
+#endif
diff --git a/src/peds/EmergencyPed.h b/src/peds/EmergencyPed.h
index 6546e902..6d3dac79 100644
--- a/src/peds/EmergencyPed.h
+++ b/src/peds/EmergencyPed.h
@@ -36,4 +36,6 @@ public:
 	void FiremanAI(void);
 	void MedicAI(void);
 };
+#ifndef PED_SKIN
 static_assert(sizeof(CEmergencyPed) == 0x554, "CEmergencyPed: error");
+#endif
diff --git a/src/peds/Ped.cpp b/src/peds/Ped.cpp
index f4cdff70..dcb167f0 100644
--- a/src/peds/Ped.cpp
+++ b/src/peds/Ped.cpp
@@ -6,6 +6,7 @@
 #include "Stats.h"
 #include "World.h"
 #include "RpAnimBlend.h"
+#include "Bones.h"
 #include "Ped.h"
 #include "Wanted.h"
 #include "PlayerPed.h"
@@ -642,6 +643,9 @@ CPed::CPed(uint32 pedType) : m_pedIK(this)
 	m_collPoly.valid = false;
 	m_fCollisionSpeed = 0.0f;
 	m_wepModelID = -1;
+#ifdef PED_SKIN
+	m_pWeaponModel = nil;
+#endif
 	CPopulation::UpdatePedCount((ePedType)m_nPedType, false);
 }
 
@@ -818,10 +822,17 @@ CPed::AddWeaponModel(int id)
 	RpAtomic *atm;
 
 	if (id != -1) {
-		atm = (RpAtomic*)CModelInfo::GetModelInfo(id)->CreateInstance();
-		RwFrameDestroy(RpAtomicGetFrame(atm));
-		RpAtomicSetFrame(atm, GetNodeFrame(PED_HANDR));
-		RpClumpAddAtomic(GetClump(), atm);
+#ifdef PED_SKIN
+		if(IsClumpSkinned(GetClump()))
+			m_pWeaponModel = (RpAtomic*)CModelInfo::GetModelInfo(id)->CreateInstance();
+		else
+#endif
+		{
+			atm = (RpAtomic*)CModelInfo::GetModelInfo(id)->CreateInstance();
+			RwFrameDestroy(RpAtomicGetFrame(atm));
+			RpAtomicSetFrame(atm, m_pFrames[PED_HANDR]->frame);
+			RpClumpAddAtomic(GetClump(), atm);
+		}
 		m_wepModelID = id;
 	}
 }
@@ -915,25 +926,28 @@ void
 CPed::RemoveBodyPart(PedNode nodeId, int8 direction)
 {
 	RwFrame *frame;
-	RwV3d pos;
+	CVector pos;
 
-	frame = GetNodeFrame(nodeId);
+	frame = m_pFrames[nodeId]->frame;
 	if (frame) {
 		if (CGame::nastyGame) {
-#ifdef TOGGLEABLE_BETA_FEATURES
-			if (bPopHeadsOnHeadshot || nodeId != PED_HEAD)
-#else
-			if (nodeId != PED_HEAD)
+#ifdef PED_SKIN
+			if(!IsClumpSkinned(GetClump()))
 #endif
-				SpawnFlyingComponent(nodeId, direction);
+			{
+#ifdef TOGGLEABLE_BETA_FEATURES
+				if (bPopHeadsOnHeadshot || nodeId != PED_HEAD)
+#else
+				if (nodeId != PED_HEAD)
+#endif
+					SpawnFlyingComponent(nodeId, direction);
 
-			RecurseFrameChildrenVisibilityCB(frame, nil);
+				RecurseFrameChildrenVisibilityCB(frame, nil);
+			}
 			pos.x = 0.0f;
 			pos.y = 0.0f;
 			pos.z = 0.0f;
-
-			for (; frame; frame = RwFrameGetParent(frame))
-				RwV3dTransformPoints(&pos, &pos, 1, RwFrameGetMatrix(frame));
+			TransformToNode(pos, PED_HEAD);
 
 			if (CEntity::GetIsOnScreen()) {
 				CParticle::AddParticle(PARTICLE_TEST, pos,
@@ -1102,10 +1116,7 @@ CPed::ClearLookFlag(void) {
 bool
 CPed::IsPedHeadAbovePos(float zOffset)
 {
-	RwMatrix mat;
-	
-	CPedIK::GetWorldMatrix(GetNodeFrame(PED_HEAD), &mat);
-	return zOffset + GetPosition().z < RwMatrixGetPos(&mat)->z;
+	return zOffset + GetPosition().z < GetNodePosition(PED_HEAD).z;
 }
 
 void
@@ -1158,7 +1169,6 @@ CPed::Attack(void)
 	CAnimBlendAssociation *weaponAnimAssoc;
 	int32 weaponAnim;
 	float animStart;
-	RwFrame *frame;
 	eWeaponType ourWeaponType;
 	float weaponAnimTime;
 	eWeaponFire ourWeaponFire;
@@ -1260,13 +1270,7 @@ CPed::Attack(void)
 
 			firePos = GetMatrix() * firePos;
 		} else if (ourWeaponType != WEAPONTYPE_UNARMED) {
-			if (weaponAnimAssoc->animId == ANIM_KICK_FLOOR)
-				frame = GetNodeFrame(PED_FOOTR);
-			else
-				frame = GetNodeFrame(PED_HANDR);
-
-			for (; frame; frame = RwFrameGetParent(frame))
-				RwV3dTransformPoints((RwV3d*)firePos, (RwV3d*)firePos, 1, RwFrameGetMatrix(frame));
+			TransformToNode(firePos, weaponAnimAssoc->animId == ANIM_KICK_FLOOR ? PED_FOOTR : PED_HANDR);
 		} else {
 			firePos = GetMatrix() * firePos;
 		}
@@ -1314,8 +1318,7 @@ CPed::Attack(void)
 		firePos = ourWeapon->m_vecFireOffset;
 
 		if (weaponAnimTime > 1.0f && weaponAnimTime - weaponAnimAssoc->timeStep <= 1.0f && weaponAnimAssoc->IsRunning()) {
-			for (frame = GetNodeFrame(PED_HANDR); frame; frame = RwFrameGetParent(frame))
-				RwV3dTransformPoints((RwV3d*)firePos, (RwV3d*)firePos, 1, RwFrameGetMatrix(frame));
+			TransformToNode(firePos, PED_HANDR);
 
 			CVector gunshellPos(
 				firePos.x - 0.6f * GetForward().x,
@@ -1415,7 +1418,17 @@ void
 CPed::RemoveWeaponModel(int modelId)
 {
 	// modelId is not used!! This function just removes the current weapon.
-	RwFrameForAllObjects(GetNodeFrame(PED_HANDR),RemoveAllModelCB,nil);
+#ifdef PED_SKIN
+	if(IsClumpSkinned(GetClump())){
+		if(m_pWeaponModel){
+			RwFrame *frm = RpAtomicGetFrame(m_pWeaponModel);
+			RpAtomicDestroy(m_pWeaponModel);
+			RwFrameDestroy(frm);
+			m_pWeaponModel = nil;
+		}
+	}else
+#endif
+		RwFrameForAllObjects(m_pFrames[PED_HANDR]->frame,RemoveAllModelCB,nil);
 	m_wepModelID = -1;
 }
 
@@ -2106,12 +2119,7 @@ CPed::PlayFootSteps(void)
 	{
 		{
 			CVector pos(0.0f, 0.0f, 0.0f);
-			RwFrame *parent = m_pFrames[PED_FOOTL]->frame;
-			while( parent )
-			{
-				RwV3dTransformPoints(pos, pos, 1, RwFrameGetMatrix(parent));
-				parent = RwFrameGetParent(parent);
-			}
+			TransformToNode(pos, PED_FOOTL);
 				
 			pos.z -= 0.1f;
 			pos += GetForward()*0.2f;
@@ -2120,12 +2128,7 @@ CPed::PlayFootSteps(void)
 
 		{
 			CVector pos(0.0f, 0.0f, 0.0f);
-			RwFrame *parent = m_pFrames[PED_FOOTR]->frame;
-			while( parent )
-			{
-				RwV3dTransformPoints(pos, pos, 1, RwFrameGetMatrix(parent));
-				parent = RwFrameGetParent(parent);
-			}
+			TransformToNode(pos, PED_FOOTR);
 				
 			pos.z -= 0.1f;
 			pos += GetForward()*0.2f;
@@ -2149,9 +2152,7 @@ CPed::PlayFootSteps(void)
 		if (stepPart != 0) {
 			DMAudio.PlayOneShot(m_audioEntityId, stepPart == 1 ? SOUND_STEP_START : SOUND_STEP_END, 1.0f);
 			CVector footPos(0.0f, 0.0f, 0.0f);
-
-			for (RwFrame *frame = GetNodeFrame(stepPart == 1 ? PED_FOOTL : PED_FOOTR); frame; frame = RwFrameGetParent(frame))
-				RwV3dTransformPoints(footPos, footPos, 1, RwFrameGetMatrix(frame));
+			TransformToNode(footPos, stepPart == 1 ? PED_FOOTL : PED_FOOTR);
 
 			CVector forward = GetForward();
 
@@ -2358,6 +2359,11 @@ CPed::SetModelIndex(uint32 mi)
 
 	// This is a mistake by R*, velocity is CVector, whereas m_vecAnimMoveDelta is CVector2D. 
 	(*RPANIMBLENDCLUMPDATA(m_rwObject))->velocity = (CVector*) &m_vecAnimMoveDelta;
+
+#ifdef PED_SKIN
+	if(modelInfo->GetHitColModel() == nil)
+		modelInfo->CreateHitColModelSkinned(GetClump());
+#endif
 }
 
 void
@@ -2517,9 +2523,31 @@ CPed::CalculateNewVelocity(void)
 		}
 
 		if (newUpperLegs.phi > -DEGTORAD(50.0f) && newUpperLegs.phi < DEGTORAD(50.0f)) {
-			newUpperLegs.theta = 0.0f;
-			m_pedIK.RotateTorso(m_pFrames[PED_UPPERLEGL], &newUpperLegs, false);
-			m_pedIK.RotateTorso(m_pFrames[PED_UPPERLEGR], &newUpperLegs, false);
+#ifdef PED_SKIN
+			if(IsClumpSkinned(GetClump())){
+/*
+				// this looks shit
+				newUpperLegs.theta = 0.0f;
+				RwV3d axis = { -1.0f, 0.0f, 0.0f };
+				RtQuatRotate(&m_pFrames[PED_UPPERLEGL]->hanimFrame->q, &axis, RADTODEG(newUpperLegs.phi), rwCOMBINEPRECONCAT);
+				RtQuatRotate(&m_pFrames[PED_UPPERLEGR]->hanimFrame->q, &axis, RADTODEG(newUpperLegs.phi), rwCOMBINEPRECONCAT);
+*/
+				newUpperLegs.theta = 0.1f;
+				RwV3d Xaxis = { 1.0f, 0.0f, 0.0f };
+				RwV3d Zaxis = { 0.0f, 0.0f, 1.0f };
+				RtQuatRotate(&m_pFrames[PED_UPPERLEGL]->hanimFrame->q, &Zaxis, RADTODEG(newUpperLegs.theta), rwCOMBINEPOSTCONCAT);
+				RtQuatRotate(&m_pFrames[PED_UPPERLEGL]->hanimFrame->q, &Xaxis, RADTODEG(newUpperLegs.phi), rwCOMBINEPOSTCONCAT);
+				RtQuatRotate(&m_pFrames[PED_UPPERLEGR]->hanimFrame->q, &Zaxis, RADTODEG(newUpperLegs.theta), rwCOMBINEPOSTCONCAT);
+				RtQuatRotate(&m_pFrames[PED_UPPERLEGR]->hanimFrame->q, &Xaxis, RADTODEG(newUpperLegs.phi), rwCOMBINEPOSTCONCAT);
+
+				bDontAcceptIKLookAts = true;
+			}else
+#endif
+			{
+				newUpperLegs.theta = 0.0f;
+				m_pedIK.RotateTorso(m_pFrames[PED_UPPERLEGL], &newUpperLegs, false);
+				m_pedIK.RotateTorso(m_pFrames[PED_UPPERLEGR], &newUpperLegs, false);
+			}
 		}
 	}
 }
@@ -5118,6 +5146,12 @@ CPed::FightStrike(CVector &touchedNodePos)
 			// He can beat us
 			if (sq(maxDistanceToBeBeaten) > potentialAttackDistance.MagnitudeSqr()) {
 
+#ifdef PED_SKIN
+				// Have to animate a skinned clump because the initial col model is useless
+				if(IsClumpSkinned(GetClump()))
+					ourCol = ((CPedModelInfo*)CModelInfo::GetModelInfo(m_modelIndex))->AnimatePedColModelSkinned(GetClump());
+				else
+#endif
 				if (nearPed->m_nPedState == PED_FALL
 					|| nearPed->m_nPedState == PED_DEAD || nearPed->m_nPedState == PED_DIE
 					|| !nearPed->IsPedHeadAbovePos(-0.3f)) {
@@ -6153,11 +6187,9 @@ CPed::Die(void)
 uint8
 CPed::DoesLOSBulletHitPed(CColPoint &colPoint)
 {
-	RwMatrix mat;
 	uint8 retVal = 2;
 
-	CPedIK::GetWorldMatrix(GetNodeFrame(PED_HEAD), &mat);
-	float headZ = RwMatrixGetPos(&mat)->z;
+	float headZ = GetNodePosition(PED_HEAD).z;
 
 	if (m_nPedState == PED_FALL)
 		retVal = 1;
@@ -6579,37 +6611,32 @@ CPed::Fight(void)
 		if (curMove.hitLevel != HITLEVEL_NULL && animTime > curMove.startFireTime && animTime <= curMove.endFireTime && m_fightState >= FIGHTSTATE_NO_MOVE) {
 
 			CVector touchingNodePos(0.0f, 0.0f, 0.0f);
-			RwFrame *touchingFrame = nil;
 
 			switch (m_lastFightMove) {
 				case FIGHTMOVE_STDPUNCH:
 				case FIGHTMOVE_PUNCHHOOK:
 				case FIGHTMOVE_BODYBLOW:
-					touchingFrame = GetNodeFrame(PED_HANDR);
+					TransformToNode(touchingNodePos, PED_HANDR);
 					break;
 				case FIGHTMOVE_IDLE:
 				case FIGHTMOVE_SHUFFLE_F:
 					break;
 				case FIGHTMOVE_KNEE:
-					touchingFrame = GetNodeFrame(PED_LOWERLEGR);
+					TransformToNode(touchingNodePos, PED_LOWERLEGR);
 					break;
 				case FIGHTMOVE_HEADBUTT:
-					touchingFrame = GetNodeFrame(PED_HEAD);
+					TransformToNode(touchingNodePos, PED_HEAD);
 					break;
 				case FIGHTMOVE_PUNCHJAB:
-					touchingFrame = GetNodeFrame(PED_HANDL);
+					TransformToNode(touchingNodePos, PED_HANDL);
 					break;
 				case FIGHTMOVE_KICK:
 				case FIGHTMOVE_LONGKICK:
 				case FIGHTMOVE_ROUNDHOUSE:
 				case FIGHTMOVE_GROUNDKICK:
-					touchingFrame = GetNodeFrame(PED_FOOTR);
+					TransformToNode(touchingNodePos, PED_FOOTR);
 					break;
 			}
-			while (touchingFrame) {
-				RwV3dTransformPoints(touchingNodePos, touchingNodePos, 1, RwFrameGetMatrix(touchingFrame));
-				touchingFrame = RwFrameGetParent(touchingFrame);
-			}
 
 			if (m_lastFightMove == FIGHTMOVE_PUNCHJAB) {
 				touchingNodePos += 0.1f * GetForward();
@@ -7091,8 +7118,7 @@ CPed::FinishLaunchCB(CAnimBlendAssociation *animAssoc, void *arg)
 
 	if (ped->bDoBloodyFootprints) {
 		CVector bloodPos(0.0f, 0.0f, 0.0f);
-		for (RwFrame *i = ped->GetNodeFrame(PED_FOOTL); i; i = RwFrameGetParent(i))
-			RwV3dTransformPoints(bloodPos, bloodPos, 1, RwFrameGetMatrix(i));
+		ped->TransformToNode(bloodPos, PED_FOOTL);
 
 		bloodPos.z -= 0.1f;
 		bloodPos += 0.2f * ped->GetForward();
@@ -7105,8 +7131,7 @@ CPed::FinishLaunchCB(CAnimBlendAssociation *animAssoc, void *arg)
 			255, 255, 0, 0, 4.0f, 3000, 1.0f);
 
 		bloodPos = CVector(0.0f, 0.0f, 0.0f);
-		for (RwFrame *j = ped->GetNodeFrame(PED_FOOTR); j; j = RwFrameGetParent(j))
-			RwV3dTransformPoints(bloodPos, bloodPos, 1, RwFrameGetMatrix(j));
+		ped->TransformToNode(bloodPos, PED_FOOTR);
 
 		bloodPos.z -= 0.1f;
 		bloodPos += 0.2f * ped->GetForward();
@@ -12647,9 +12672,67 @@ CPed::Render(void)
 	if (!bInVehicle || m_nPedState == PED_EXIT_CAR || m_nPedState == PED_DRAG_FROM_CAR ||
 		bRenderPedInCar && sq(25.0f * TheCamera.LODDistMultiplier) >= (TheCamera.GetPosition() - GetPosition()).MagnitudeSqr()) {
 		CEntity::Render();
+
+#ifdef PED_SKIN
+		if(IsClumpSkinned(GetClump())){
+			renderLimb(PED_HEAD);
+			renderLimb(PED_HANDL);
+			renderLimb(PED_HANDR);
+		}
+		if(m_pWeaponModel && IsClumpSkinned(GetClump())){
+			RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump());
+			int idx = RpHAnimIDGetIndex(hier, m_pFrames[PED_HANDR]->nodeID);
+			RwMatrix *mat = &RpHAnimHierarchyGetMatrixArray(hier)[idx];
+			RwFrame *frame = RpAtomicGetFrame(m_pWeaponModel);
+			*RwFrameGetMatrix(frame) = *mat;
+			RwFrameUpdateObjects(frame);
+			RpAtomicRender(m_pWeaponModel);
+		}
+#endif
 	}
 }
 
+#ifdef PED_SKIN
+static RpMaterial*
+SetLimbAlphaCB(RpMaterial *material, void *data)
+{
+	((RwRGBA*)RpMaterialGetColor(material))->alpha = *(uint8*)data;
+	return material;
+}
+
+void
+CPed::renderLimb(int node)
+{
+	RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump());
+	int idx = RpHAnimIDGetIndex(hier, m_pFrames[node]->nodeID);
+	RwMatrix *mat = &RpHAnimHierarchyGetMatrixArray(hier)[idx];
+	CPedModelInfo *mi = (CPedModelInfo*)CModelInfo::GetModelInfo(m_modelIndex);
+	RpAtomic *atomic;
+	switch(node){
+	case PED_HEAD:
+		atomic = mi->getHead();
+		break;
+	case PED_HANDL:
+		atomic = mi->getLeftHand();
+		break;
+	case PED_HANDR:
+		atomic = mi->getRightHand();
+		break;
+	default:
+		return;
+	}
+	if(atomic == nil)
+		return;
+
+	RwFrame *frame = RpAtomicGetFrame(atomic);
+	*RwFrameGetMatrix(frame) = *mat;
+	RwFrameUpdateObjects(frame);
+	int alpha = CVisibilityPlugins::GetClumpAlpha(GetClump());
+	RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), SetLimbAlphaCB, &alpha);
+	RpAtomicRender(atomic);
+}
+#endif
+
 void
 CPed::ProcessObjective(void)
 {
@@ -15065,12 +15148,26 @@ CPed::PreRender(void)
 		CTimeCycle::m_fShadowFrontX[CTimeCycle::m_CurrentStoredValue], CTimeCycle::m_fShadowFrontY[CTimeCycle::m_CurrentStoredValue],
 		CTimeCycle::m_fShadowSideX[CTimeCycle::m_CurrentStoredValue], CTimeCycle::m_fShadowSideY[CTimeCycle::m_CurrentStoredValue]);
 
+#ifdef PED_SKIN
+	if(IsClumpSkinned(GetClump())){
+		UpdateRpHAnim();
+
+		if(bBodyPartJustCameOff && m_bodyPartBleeding == PED_HEAD){
+			// scale head to 0 if shot off
+			RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump());
+			int32 idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_HEAD));
+			RwMatrix *head = &RpHAnimHierarchyGetMatrixArray(hier)[idx];
+			RwV3d zero = { 0.0f, 0.0f, 0.0f };
+			RwMatrixScale(head, &zero, rwCOMBINEPRECONCAT);
+		}
+	}
+#endif
+
 	if (bBodyPartJustCameOff && bIsPedDieAnimPlaying && m_bodyPartBleeding != -1 && (CTimer::GetFrameCounter() & 7) > 3) {
 		CVector bloodDir(0.0f, 0.0f, 0.0f);
 		CVector bloodPos(0.0f, 0.0f, 0.0f);
 
-		for (RwFrame *frame = GetNodeFrame(m_bodyPartBleeding); frame; frame = RwFrameGetParent(frame))
-			RwV3dTransformPoints(bloodPos, bloodPos, 1, RwFrameGetMatrix(frame));
+		TransformToNode(bloodPos, m_bodyPartBleeding);
 
 		switch (m_bodyPartBleeding) {
 			case PED_HEAD:
@@ -16216,11 +16313,10 @@ CPed::StartFightDefend(uint8 direction, uint8 hitLevel, uint8 unk)
 				}
 			}
 			if (CGame::nastyGame) {
-				RwMatrix headMat;
-				CPedIK::GetWorldMatrix(GetNodeFrame(PED_HEAD), &headMat);
+				CVector headPos = GetNodePosition(PED_HEAD);
 				for(int i = 0; i < 4; ++i) {
 					CVector bloodDir(0.0f, 0.0f, 0.1f);
-					CVector bloodPos = headMat.pos - 0.2f * GetForward();
+					CVector bloodPos = headPos - 0.2f * GetForward();
 					CParticle::AddParticle(PARTICLE_BLOOD, bloodPos, bloodDir, nil, 0.0f, 0, 0, 0, 0);
 				}
 			}
@@ -16792,6 +16888,10 @@ CPed::SpawnFlyingComponent(int pedNode, int8 direction)
 	if (CObject::nNoTempObjects >= NUMTEMPOBJECTS)
 		return nil;
 
+#ifdef PED_SKIN
+	assert(!IsClumpSkinned(GetClump()));
+#endif
+
 	CObject *obj = new CObject();
 	if (!obj)
 		return nil;
@@ -16799,12 +16899,12 @@ CPed::SpawnFlyingComponent(int pedNode, int8 direction)
 	RwFrame *frame = RwFrameCreate();
 	RpClump *clump = RpClumpCreate();
 	RpClumpSetFrame(clump, frame);
-	RwMatrix *matrix = RwFrameGetLTM(GetNodeFrame(pedNode));
+	RwMatrix *matrix = RwFrameGetLTM(m_pFrames[pedNode]->frame);
 	*RwFrameGetMatrix(frame) = *matrix;
 
 	flyingClumpTemp = clump;
-	RwFrameForAllObjects(GetNodeFrame(pedNode), CloneAtomicToFrameCB, frame);
-	RwFrameForAllChildren(GetNodeFrame(pedNode), RecurseFrameChildrenToCloneCB, frame);
+	RwFrameForAllObjects(m_pFrames[pedNode]->frame, CloneAtomicToFrameCB, frame);
+	RwFrameForAllChildren(m_pFrames[pedNode]->frame, RecurseFrameChildrenToCloneCB, frame);
 	flyingClumpTemp = nil;
 	switch (pedNode) {
 		case PED_HEAD:
diff --git a/src/peds/Ped.h b/src/peds/Ped.h
index fd454528..f4caafd8 100644
--- a/src/peds/Ped.h
+++ b/src/peds/Ped.h
@@ -1,5 +1,6 @@
 #pragma once
 
+#include "RwHelper.h"
 #include "AnimManager.h"
 #include "Crime.h"
 #include "EventList.h"
@@ -380,9 +381,11 @@ public:
 	uint32 bFallenDown : 1;
 #ifdef VC_PED_PORTS
 	uint32 bSomeVCflag1 : 1;
-#else
-	uint32 m_ped_flagI20 : 1;
 #endif
+#ifdef PED_SKIN
+	uint32 bDontAcceptIKLookAts : 1;	// TODO: find uses of this
+#endif
+	// our own flags
 	uint32 m_ped_flagI40 : 1; // bMakePedsRunToPhonesToReportCrimes makes use of this as runover by car indicator
 	uint32 m_ped_flagI80 : 1; // KANGAROO_CHEAT define makes use of this as cheat toggle 
 
@@ -401,6 +404,10 @@ public:
 	CEntity* m_pEventEntity;
 	float m_fAngleToEvent;
 	AnimBlendFrameData *m_pFrames[PED_NODE_MAX];
+#ifdef PED_SKIN
+	// stored inside the clump with non-skin ped
+	RpAtomic *m_pWeaponModel;
+#endif
 	AssocGroupId m_animGroup;
 	CAnimBlendAssociation *m_pVehicleAnim;
 	CVector2D m_vecAnimMoveDelta;
@@ -730,7 +737,6 @@ public:
 	static void PedSetQuickDraggedOutCarPositionCB(CAnimBlendAssociation *assoc, void *arg);
 	static void PedSetDraggedOutCarPositionCB(CAnimBlendAssociation *assoc, void *arg);
 
-	// functions that I see unnecessary to hook
 	bool IsPlayer(void);
 	bool UseGroundColModel(void);
 	bool CanSetPedState(void);
@@ -780,7 +786,6 @@ public:
 	bool HasWeapon(uint8 weaponType) { return m_weapons[weaponType].m_eWeaponType == weaponType; }
 	CWeapon &GetWeapon(uint8 weaponType) { return m_weapons[weaponType]; }
 	CWeapon *GetWeapon(void) { return &m_weapons[m_currentWeapon]; }
-	RwFrame *GetNodeFrame(int nodeId) { return m_pFrames[nodeId]->frame; }
 
 	PedState GetPedState(void) { return m_nPedState; }
 	void SetPedState(PedState state) { m_nPedState = state; }
@@ -815,6 +820,44 @@ public:
 			SetMoveState(PEDMOVE_WALK);
 	}
 
+	// Using this to abstract nodes of skinned and non-skinned meshes
+	CVector GetNodePosition(int32 node)
+	{
+#ifdef PED_SKIN
+		if(IsClumpSkinned(GetClump())){
+			RwV3d pos = { 0.0f, 0.0f, 0.0f };
+			RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump());
+			int32 idx = RpHAnimIDGetIndex(hier, m_pFrames[node]->nodeID);
+			RwMatrix *mats = RpHAnimHierarchyGetMatrixArray(hier);
+			// this is just stupid
+			//RwV3dTransformPoints(&pos, &pos, 1, &mats[idx]);
+			pos = mats[idx].pos;
+			return pos;
+		}else
+#endif
+		{
+			RwMatrix mat;
+			CPedIK::GetWorldMatrix(m_pFrames[node]->frame, &mat);
+			return mat.pos;
+		}
+	}
+	void TransformToNode(CVector &pos, int32 node)
+	{
+#ifdef PED_SKIN
+		if(IsClumpSkinned(GetClump())){
+			RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump());
+			int32 idx = RpHAnimIDGetIndex(hier, m_pFrames[node]->nodeID);
+			RwMatrix *mats = RpHAnimHierarchyGetMatrixArray(hier);
+			RwV3dTransformPoints((RwV3d*)&pos, (RwV3d*)&pos, 1, &mats[idx]);
+		}else
+#endif
+		{
+			RwFrame *frame;
+			for (frame = m_pFrames[node]->frame; frame; frame = RwFrameGetParent(frame))
+				RwV3dTransformPoints((RwV3d*)&pos, (RwV3d*)&pos, 1, RwFrameGetMatrix(frame));
+		}
+	}
+
 	// set by 0482:set_threat_reaction_range_multiplier opcode
 	static uint16 nThreatReactionRangeMultiplier;
 
@@ -836,6 +879,10 @@ public:
 	static void SwitchDebugDisplay(void);
 	void DebugRenderOnePedText(void);
 #endif
+
+#ifdef PED_SKIN
+	void renderLimb(int node);
+#endif
 };
 
 class cPedParams
@@ -849,6 +896,7 @@ public:
 
 void FinishFuckUCB(CAnimBlendAssociation *assoc, void *arg);
 
+#ifndef PED_SKIN
 static_assert(offsetof(CPed, m_nPedState) == 0x224, "CPed: error");
 static_assert(offsetof(CPed, m_pCurSurface) == 0x2FC, "CPed: error");
 static_assert(offsetof(CPed, m_pMyVehicle) == 0x310, "CPed: error");
@@ -861,3 +909,4 @@ static_assert(offsetof(CPed, m_bodyPartBleeding) == 0x4F2, "CPed: error");
 static_assert(offsetof(CPed, m_pedInObjective) == 0x16C, "CPed: error");
 static_assert(offsetof(CPed, m_pEventEntity) == 0x19C, "CPed: error");
 static_assert(sizeof(CPed) == 0x53C, "CPed: error");
+#endif
diff --git a/src/peds/PedIK.cpp b/src/peds/PedIK.cpp
index 1464c4e8..3db3dc0f 100644
--- a/src/peds/PedIK.cpp
+++ b/src/peds/PedIK.cpp
@@ -1,5 +1,6 @@
 #include "common.h"
 
+#include "Bones.h"
 #include "Camera.h"
 #include "PedIK.h"
 #include "Ped.h"
@@ -12,6 +13,10 @@ LimbMovementInfo CPedIK::ms_headRestoreInfo = { DEGTORAD(90.0f), DEGTORAD(-90.0f
 LimbMovementInfo CPedIK::ms_upperArmInfo = { DEGTORAD(20.0f), DEGTORAD(-100.0f), DEGTORAD(20.0f), DEGTORAD(70.0f), DEGTORAD(-70.0f), DEGTORAD(10.0f) };
 LimbMovementInfo CPedIK::ms_lowerArmInfo = { DEGTORAD(80.0f), DEGTORAD(0.0f), DEGTORAD(20.0f), DEGTORAD(90.0f), DEGTORAD(-90.0f), DEGTORAD(5.0f) };
 
+const RwV3d XaxisIK = { 1.0f, 0.0f, 0.0f};
+const RwV3d YaxisIK = { 0.0f, 1.0f, 0.0f};
+const RwV3d ZaxisIK = { 0.0f, 0.0f, 1.0f};
+
 CPedIK::CPedIK(CPed *ped)
 {
 	m_ped = ped;
@@ -26,57 +31,104 @@ CPedIK::CPedIK(CPed *ped)
 	m_lowerArmOrient.theta = 0.0f;
 }
 
-void
-CPedIK::RotateTorso(AnimBlendFrameData *animBlend, LimbOrientation *limb, bool changeRoll)
+#ifdef PED_SKIN
+inline RwMatrix*
+GetComponentMatrix(CPed *ped, int32 node)
 {
-	RwFrame *f = animBlend->frame;
-	RwMatrix *mat = CPedIK::GetWorldMatrix(RwFrameGetParent(f), RwMatrixCreate());
+	RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(ped->GetClump());
+	int idx = RpHAnimIDGetIndex(hier, ped->m_pFrames[node]->nodeID);
+	RwMatrix *mats = RpHAnimHierarchyGetMatrixArray(hier);
+	return &mats[idx];
+}
+#endif
 
-	RwV3d upVector = { mat->right.z, mat->up.z, mat->at.z };
-	RwV3d rightVector;
-	RwV3d pos = RwFrameGetMatrix(f)->pos;
+void
+CPedIK::RotateTorso(AnimBlendFrameData *node, LimbOrientation *limb, bool changeRoll)
+{
+#ifdef PED_SKIN
+	if(IsClumpSkinned(m_ped->GetClump())){
+		RtQuat *q = &node->hanimFrame->q;
+#ifndef FIX_BUGS
+		// this is what the game does (also VC), but it does not look great
+		RtQuatRotate(q, &XaxisIK, RADTODEG(limb->phi), rwCOMBINEPRECONCAT);
+		RtQuatRotate(q, &ZaxisIK, RADTODEG(limb->theta), rwCOMBINEPRECONCAT);	// pitch
+#else
+		// copied the code from the non-skinned case
+		// this seems to work ok
 
-	// rotation == 0 -> looking in y direction
-	// left? vector
-	float c = Cos(m_ped->m_fRotationCur);
-	float s = Sin(m_ped->m_fRotationCur);
-	rightVector.x = -(c*mat->right.x + s*mat->right.y);
-	rightVector.y = -(c*mat->up.x + s*mat->up.y);
-	rightVector.z = -(c*mat->at.x + s*mat->at.y);
+		// We can't get the parent matrix of an hanim frame but
+		// this function is always called with PED_MID, so we know the parent frame.
+		// Trouble is that PED_MID is "Smid" on PS2/PC but BONE_torso on mobile/xbox...
+		// so this doesn't exactly do what we'd like anyway
+		RwMatrix *mat = GetComponentMatrix(m_ped, PED_MID);
 
-	if(changeRoll){
-		// Used when aiming only involves over the legs.(canAimWithArm)
-		// Automatically changes roll(forward rotation) axis of the parts above upper legs while moving, based on position of upper legs.
-		// Not noticeable in normal conditions...
+		RwV3d vec1, vec2;
+		vec1.x = mat->right.z;
+		vec1.y = mat->up.z;
+		vec1.z = mat->at.z;
+		float c = Cos(m_ped->m_fRotationCur);
+		float s = Sin(m_ped->m_fRotationCur);
+		vec2.x = -(c*mat->right.x + s*mat->right.y);
+		vec2.y = -(c*mat->up.x + s*mat->up.y);
+		vec2.z = -(c*mat->at.x + s*mat->at.y);
 
-		RwV3d forwardVector;
-		CVector inversedForward = CrossProduct(CVector(0.0f, 0.0f, 1.0f), mat->up);
-		inversedForward.Normalise();
-		float dotProduct = DotProduct(mat->at, inversedForward);
-		if(dotProduct > 1.0f) dotProduct = 1.0f;
-		if(dotProduct < -1.0f) dotProduct = -1.0f;
-		float alpha = Acos(dotProduct);
+		// Not sure what exactly to do here
+		RtQuatRotate(q, &vec1, RADTODEG(limb->phi), rwCOMBINEPRECONCAT);
+		RtQuatRotate(q, &vec2, RADTODEG(limb->theta), rwCOMBINEPRECONCAT);
+#endif
+		m_ped->bDontAcceptIKLookAts = true;
+	}else
+#endif
+	{
+		RwFrame *f = node->frame;
+		RwMatrix *mat = GetWorldMatrix(RwFrameGetParent(f), RwMatrixCreate());
 
-		if(mat->at.z < 0.0f)
-			alpha = -alpha;
+		RwV3d upVector = { mat->right.z, mat->up.z, mat->at.z };
+		RwV3d rightVector;
+		RwV3d pos = RwFrameGetMatrix(f)->pos;
 
-		forwardVector.x = s * mat->right.x - c * mat->right.y;
-		forwardVector.y = s * mat->up.x - c * mat->up.y;
-		forwardVector.z = s * mat->at.x - c * mat->at.y;
+		// rotation == 0 -> looking in y direction
+		// left? vector
+		float c = Cos(m_ped->m_fRotationCur);
+		float s = Sin(m_ped->m_fRotationCur);
+		rightVector.x = -(c*mat->right.x + s*mat->right.y);
+		rightVector.y = -(c*mat->up.x + s*mat->up.y);
+		rightVector.z = -(c*mat->at.x + s*mat->at.y);
 
-		float curYaw, curPitch;
-		CPedIK::ExtractYawAndPitchWorld(mat, &curYaw, &curPitch);
-		RwMatrixRotate(RwFrameGetMatrix(f), &rightVector, RADTODEG(limb->theta), rwCOMBINEPOSTCONCAT);
-		RwMatrixRotate(RwFrameGetMatrix(f), &upVector, RADTODEG(limb->phi - (curYaw - m_ped->m_fRotationCur)), rwCOMBINEPOSTCONCAT);
-		RwMatrixRotate(RwFrameGetMatrix(f), &forwardVector, RADTODEG(alpha), rwCOMBINEPOSTCONCAT);
-	}else{
-		// pitch
-		RwMatrixRotate(RwFrameGetMatrix(f), &rightVector, RADTODEG(limb->theta), rwCOMBINEPOSTCONCAT);
-		// yaw
-		RwMatrixRotate(RwFrameGetMatrix(f), &upVector, RADTODEG(limb->phi), rwCOMBINEPOSTCONCAT);
+		if(changeRoll){
+			// Used when aiming only involves over the legs.(canAimWithArm)
+			// Automatically changes roll(forward rotation) axis of the parts above upper legs while moving, based on position of upper legs.
+			// Not noticeable in normal conditions...
+
+			RwV3d forwardVector;
+			CVector inversedForward = CrossProduct(CVector(0.0f, 0.0f, 1.0f), mat->up);
+			inversedForward.Normalise();
+			float dotProduct = DotProduct(mat->at, inversedForward);
+			if(dotProduct > 1.0f) dotProduct = 1.0f;
+			if(dotProduct < -1.0f) dotProduct = -1.0f;
+			float alpha = Acos(dotProduct);
+
+			if(mat->at.z < 0.0f)
+				alpha = -alpha;
+
+			forwardVector.x = s * mat->right.x - c * mat->right.y;
+			forwardVector.y = s * mat->up.x - c * mat->up.y;
+			forwardVector.z = s * mat->at.x - c * mat->at.y;
+
+			float curYaw, curPitch;
+			ExtractYawAndPitchWorld(mat, &curYaw, &curPitch);
+			RwMatrixRotate(RwFrameGetMatrix(f), &rightVector, RADTODEG(limb->theta), rwCOMBINEPOSTCONCAT);
+			RwMatrixRotate(RwFrameGetMatrix(f), &upVector, RADTODEG(limb->phi - (curYaw - m_ped->m_fRotationCur)), rwCOMBINEPOSTCONCAT);
+			RwMatrixRotate(RwFrameGetMatrix(f), &forwardVector, RADTODEG(alpha), rwCOMBINEPOSTCONCAT);
+		}else{
+			// pitch
+			RwMatrixRotate(RwFrameGetMatrix(f), &rightVector, RADTODEG(limb->theta), rwCOMBINEPOSTCONCAT);
+			// yaw
+			RwMatrixRotate(RwFrameGetMatrix(f), &upVector, RADTODEG(limb->phi), rwCOMBINEPOSTCONCAT);
+		}
+		RwFrameGetMatrix(f)->pos = pos;
+		RwMatrixDestroy(mat);
 	}
-	RwFrameGetMatrix(f)->pos = pos;
-	RwMatrixDestroy(mat);
 }
 
 void
@@ -85,12 +137,24 @@ CPedIK::GetComponentPosition(RwV3d *pos, uint32 node)
 	RwFrame *f;
 	RwMatrix *mat;
 
-	f = m_ped->GetNodeFrame(node);
-	mat = RwFrameGetMatrix(f);
-	*pos = mat->pos;
+#ifdef PED_SKIN
+	if(IsClumpSkinned(m_ped->GetClump())){
+		pos->x = 0.0f;
+		pos->y = 0.0f;
+		pos->z = 0.0f;
+		mat = GetComponentMatrix(m_ped, node);
+		// could just copy the position out of the matrix...
+		RwV3dTransformPoints(pos, pos, 1, mat);
+	}else
+#endif
+	{
+		f = m_ped->m_pFrames[node]->frame;
+		mat = RwFrameGetMatrix(f);
+		*pos = mat->pos;
 
-	for (f = RwFrameGetParent(f); f; f = RwFrameGetParent(f))
-		RwV3dTransformPoints(pos, pos, 1, RwFrameGetMatrix(f));
+		for (f = RwFrameGetParent(f); f; f = RwFrameGetParent(f))
+			RwV3dTransformPoints(pos, pos, 1, RwFrameGetMatrix(f));
+	}
 }
 
 RwMatrix*
@@ -157,50 +221,94 @@ CPedIK::RestoreGunPosn(void)
 	return limbStatus == ANGLES_SET_EXACTLY;
 }
 
+#ifdef PED_SKIN
+void
+CPedIK::RotateHead(void)
+{
+	RtQuat *q = &m_ped->m_pFrames[PED_HEAD]->hanimFrame->q;
+	RtQuatRotate(q, &XaxisIK, RADTODEG(m_headOrient.phi), rwCOMBINEREPLACE);
+	RtQuatRotate(q, &ZaxisIK, RADTODEG(m_headOrient.theta), rwCOMBINEPOSTCONCAT);
+	m_ped->bDontAcceptIKLookAts = true;
+}
+#endif
+
 bool
 CPedIK::LookInDirection(float phi, float theta)
 {
 	bool success = true;
-	RwFrame *frame = m_ped->GetNodeFrame(PED_HEAD);
-	RwMatrix *frameMat = RwFrameGetMatrix(frame);
+	float yaw, pitch;
+#ifdef PED_SKIN
+	if(IsClumpSkinned(m_ped->GetClump())){
+		if (!(m_ped->m_pFrames[PED_HEAD]->flag & AnimBlendFrameData::IGNORE_ROTATION)) {
+			m_ped->m_pFrames[PED_HEAD]->flag |= AnimBlendFrameData::IGNORE_ROTATION;
+			ExtractYawAndPitchLocalSkinned(m_ped->m_pFrames[PED_HEAD], &m_headOrient.phi, &m_headOrient.theta);
+		}
+
+		// parent of head is torso
+		RwMatrix worldMat = *GetComponentMatrix(m_ped, BONE_torso);
+		ExtractYawAndPitchWorld(&worldMat, &yaw, &pitch);
+
+		LimbMoveStatus headStatus = MoveLimb(m_headOrient, CGeneral::LimitRadianAngle(phi - yaw),
+			CGeneral::LimitRadianAngle(DEGTORAD(10.0f)), ms_headInfo);
+		if (headStatus == ANGLES_SET_TO_MAX)
+			success = false;
+
+		if (headStatus != ANGLES_SET_EXACTLY){
+			if (!(m_flags & LOOKAROUND_HEAD_ONLY)){
+				if (MoveLimb(m_torsoOrient, CGeneral::LimitRadianAngle(phi), theta, ms_torsoInfo))
+					success = true;
+			}else{
+				RotateHead();
+				return success;
+			}
+		}
+
+		if (!(m_flags & LOOKAROUND_HEAD_ONLY))
+			RotateTorso(m_ped->m_pFrames[PED_MID], &m_torsoOrient, false);
+		RotateHead();
+	}else
+#endif
+	{
+		RwFrame *frame = m_ped->m_pFrames[PED_HEAD]->frame;
+		RwMatrix *frameMat = RwFrameGetMatrix(frame);
+
+		if (!(m_ped->m_pFrames[PED_HEAD]->flag & AnimBlendFrameData::IGNORE_ROTATION)) {
+			m_ped->m_pFrames[PED_HEAD]->flag |= AnimBlendFrameData::IGNORE_ROTATION;
+			ExtractYawAndPitchLocal(frameMat, &m_headOrient.phi, &m_headOrient.theta);
+		}
+
+		RwMatrix *worldMat = RwMatrixCreate();
+		worldMat = GetWorldMatrix(RwFrameGetParent(frame), worldMat);
+
+		ExtractYawAndPitchWorld(worldMat, &yaw, &pitch);
+		RwMatrixDestroy(worldMat);
+
+		yaw += m_torsoOrient.phi;
+		float neededPhiTurn = CGeneral::LimitRadianAngle(phi - yaw);
+		pitch *= Cos(neededPhiTurn);
+
+		float neededThetaTurn = CGeneral::LimitRadianAngle(theta - pitch);
+		LimbMoveStatus headStatus = MoveLimb(m_headOrient, neededPhiTurn, neededThetaTurn, ms_headInfo);
+		if (headStatus == ANGLES_SET_TO_MAX)
+			success = false;
+
+		if (headStatus != ANGLES_SET_EXACTLY && !(m_flags & LOOKAROUND_HEAD_ONLY)) {
+			float remainingTurn = CGeneral::LimitRadianAngle(phi - m_ped->m_fRotationCur);
+			if (MoveLimb(m_torsoOrient, remainingTurn, theta, ms_torsoInfo))
+				success = true;
+		}
+		CMatrix nextFrame = CMatrix(frameMat);
+		CVector framePos = nextFrame.GetPosition();
+
+		nextFrame.SetRotateZ(m_headOrient.theta);
+		nextFrame.RotateX(m_headOrient.phi);
+		nextFrame.GetPosition() += framePos;
+		nextFrame.UpdateRW();
+
+		if (!(m_flags & LOOKAROUND_HEAD_ONLY))
+			RotateTorso(m_ped->m_pFrames[PED_MID], &m_torsoOrient, false);
 
-	if (!(m_ped->m_pFrames[PED_HEAD]->flag & AnimBlendFrameData::IGNORE_ROTATION)) {
-		m_ped->m_pFrames[PED_HEAD]->flag |= AnimBlendFrameData::IGNORE_ROTATION;
-		CPedIK::ExtractYawAndPitchLocal(frameMat, &m_headOrient.phi, &m_headOrient.theta);
 	}
-
-	RwMatrix *worldMat = RwMatrixCreate();
-	worldMat = CPedIK::GetWorldMatrix(RwFrameGetParent(frame), worldMat);
-
-	float alpha, beta;
-	CPedIK::ExtractYawAndPitchWorld(worldMat, &alpha, &beta);
-	RwMatrixDestroy(worldMat);
-
-	alpha += m_torsoOrient.phi;
-	float neededPhiTurn = CGeneral::LimitRadianAngle(phi - alpha);
-	beta *= cos(neededPhiTurn);
-
-	float neededThetaTurn = CGeneral::LimitRadianAngle(theta - beta);
-	LimbMoveStatus headStatus = CPedIK::MoveLimb(m_headOrient, neededPhiTurn, neededThetaTurn, ms_headInfo);
-	if (headStatus == ANGLES_SET_TO_MAX)
-		success = false;
-
-	if (headStatus != ANGLES_SET_EXACTLY && !(m_flags & LOOKAROUND_HEAD_ONLY)) {
-		float remainingTurn = CGeneral::LimitRadianAngle(phi - m_ped->m_fRotationCur);
-		if (CPedIK::MoveLimb(m_torsoOrient, remainingTurn, theta, ms_torsoInfo))
-			success = true;
-	}
-	CMatrix nextFrame = CMatrix(frameMat);
-	CVector framePos = nextFrame.GetPosition();
-
-	nextFrame.SetRotateZ(m_headOrient.theta);
-	nextFrame.RotateX(m_headOrient.phi);
-	nextFrame.GetPosition() += framePos;
-	nextFrame.UpdateRW();
-
-	if (!(m_flags & LOOKAROUND_HEAD_ONLY))
-		RotateTorso(m_ped->m_pFrames[PED_MID], &m_torsoOrient, false);
-
 	return success;
 }
 
@@ -234,10 +342,24 @@ CPedIK::PointGunInDirection(float phi, float theta)
 		if (m_flags & AIMS_WITH_ARM && m_torsoOrient.phi * m_upperArmOrient.phi < 0.0f)
 			MoveLimb(m_torsoOrient, 0.0f, m_torsoOrient.theta, ms_torsoInfo);
 	} else {
-		RwMatrix *matrix = GetWorldMatrix(RwFrameGetParent(m_ped->GetNodeFrame(PED_UPPERARMR)), RwMatrixCreate());
+		// Unused code
+		RwMatrix *matrix;
 		float yaw, pitch;
-		ExtractYawAndPitchWorld(matrix, &yaw, &pitch);
-		RwMatrixDestroy(matrix);
+#ifdef PED_SKIN
+		if(IsClumpSkinned(m_ped->GetClump())){
+			matrix = RwMatrixCreate();
+			*matrix = *GetComponentMatrix(m_ped, PED_UPPERARMR);
+			ExtractYawAndPitchWorld(matrix, &yaw, &pitch);
+			RwMatrixDestroy(matrix);
+		}else
+#endif
+		{
+			matrix = GetWorldMatrix(RwFrameGetParent(m_ped->m_pFrames[PED_UPPERARMR]->frame), RwMatrixCreate());
+			ExtractYawAndPitchWorld(matrix, &yaw, &pitch);
+			RwMatrixDestroy(matrix);
+		}
+		//
+
 		LimbMoveStatus status = MoveLimb(m_torsoOrient, angle, theta, ms_torsoInfo);
 		if (status == ANGLES_SET_TO_MAX)
 			result = false;
@@ -255,24 +377,56 @@ bool
 CPedIK::PointGunInDirectionUsingArm(float phi, float theta)
 {
 	bool result = false;
-	RwFrame *frame = m_ped->GetNodeFrame(PED_UPPERARMR);
-	RwMatrix *matrix = GetWorldMatrix(RwFrameGetParent(frame), RwMatrixCreate());
-
-	RwV3d upVector = { matrix->right.z, matrix->up.z, matrix->at.z };
 
+	RwV3d upVector;	// only for non-skinned
+	RwMatrix *matrix;
 	float yaw, pitch;
-	ExtractYawAndPitchWorld(matrix, &yaw, &pitch);
-	RwMatrixDestroy(matrix);
+#ifdef PED_SKIN
+	if(IsClumpSkinned(m_ped->GetClump())){
+		matrix = RwMatrixCreate();
+		*matrix = *GetComponentMatrix(m_ped, PED_UPPERARMR);
+		ExtractYawAndPitchWorld(matrix, &yaw, &pitch);
+		RwMatrixDestroy(matrix);
+	}else
+#endif
+	{
+		RwFrame *frame = m_ped->m_pFrames[PED_UPPERARMR]->frame;
+		matrix = GetWorldMatrix(RwFrameGetParent(frame), RwMatrixCreate());
+
+		// with PED_SKIN this is actually done below (with a memory leak)
+		upVector.x = matrix->right.z;
+		upVector.y = matrix->up.z;
+		upVector.z = matrix->at.z;
+
+		ExtractYawAndPitchWorld(matrix, &yaw, &pitch);
+		RwMatrixDestroy(matrix);
+	}
 
 	RwV3d rightVector = { 0.0f, 0.0f, 1.0f };
 	RwV3d forwardVector = { 1.0f, 0.0f, 0.0f };
 
-	float uaPhi = phi - m_torsoOrient.phi - DEGTORAD(15.0f);
-	LimbMoveStatus uaStatus = MoveLimb(m_upperArmOrient, uaPhi, CGeneral::LimitRadianAngle(theta - pitch), ms_upperArmInfo);
+	float uaPhi, uaTheta;
+#ifdef PED_SKIN
+	if(IsClumpSkinned(m_ped->GetClump())){
+		uaPhi = phi;
+		uaTheta = theta + DEGTORAD(10.0f);
+	}else
+#endif
+	{
+		uaPhi = phi - m_torsoOrient.phi - DEGTORAD(15.0f);
+		uaTheta = CGeneral::LimitRadianAngle(theta - pitch);
+	}
+	LimbMoveStatus uaStatus = MoveLimb(m_upperArmOrient, uaPhi, uaTheta, ms_upperArmInfo);
 	if (uaStatus == ANGLES_SET_EXACTLY) {
 		m_flags |= GUN_POINTED_SUCCESSFULLY;
 		result = true;
 	}
+
+#ifdef PED_SKIN
+	// this code is completely missing on xbox & android, but we can keep it with the check
+	// TODO? implement it for skinned geometry?
+	if(!IsClumpSkinned(m_ped->GetClump()))
+#endif
 	if (uaStatus == ANGLES_SET_TO_MAX) {
 		float laPhi = uaPhi - m_upperArmOrient.phi;
 
@@ -286,17 +440,29 @@ CPedIK::PointGunInDirectionUsingArm(float phi, float theta)
 			m_flags |= GUN_POINTED_SUCCESSFULLY;
 			result = true;
 		}
-		RwFrame *child = GetFirstChild(frame);
+		RwFrame *child = GetFirstChild(m_ped->m_pFrames[PED_UPPERARMR]->frame);
 		RwV3d pos = RwFrameGetMatrix(child)->pos;
 		RwMatrixRotate(RwFrameGetMatrix(child), &forwardVector, RADTODEG(m_lowerArmOrient.theta), rwCOMBINEPOSTCONCAT);
 		RwMatrixRotate(RwFrameGetMatrix(child), &rightVector, RADTODEG(-m_lowerArmOrient.phi), rwCOMBINEPOSTCONCAT);
 		RwFrameGetMatrix(child)->pos = pos;
 	}
 
-	RwV3d pos = RwFrameGetMatrix(frame)->pos;
-	RwMatrixRotate(RwFrameGetMatrix(frame), &rightVector, RADTODEG(m_upperArmOrient.theta), rwCOMBINEPOSTCONCAT);
-	RwMatrixRotate(RwFrameGetMatrix(frame), &upVector, RADTODEG(m_upperArmOrient.phi), rwCOMBINEPOSTCONCAT);
-	RwFrameGetMatrix(frame)->pos = pos;
+#ifdef PED_SKIN
+	if(IsClumpSkinned(m_ped->GetClump())){
+		RtQuat *q = &m_ped->m_pFrames[PED_UPPERARMR]->hanimFrame->q;
+		RtQuatRotate(q, &XaxisIK, RADTODEG(m_upperArmOrient.phi), rwCOMBINEPOSTCONCAT);
+		RtQuatRotate(q, &ZaxisIK, RADTODEG(m_upperArmOrient.theta), rwCOMBINEPOSTCONCAT);
+		m_ped->bDontAcceptIKLookAts = true;
+	}else
+#endif
+	{
+		RwFrame *frame = m_ped->m_pFrames[PED_UPPERARMR]->frame;
+		// with PED_SKIN we're also getting upVector here
+		RwV3d pos = RwFrameGetMatrix(frame)->pos;
+		RwMatrixRotate(RwFrameGetMatrix(frame), &rightVector, RADTODEG(m_upperArmOrient.theta), rwCOMBINEPOSTCONCAT);
+		RwMatrixRotate(RwFrameGetMatrix(frame), &upVector, RADTODEG(m_upperArmOrient.phi), rwCOMBINEPOSTCONCAT);
+		RwFrameGetMatrix(frame)->pos = pos;
+	}
 	return result;
 }
 
@@ -314,28 +480,42 @@ bool
 CPedIK::RestoreLookAt(void)
 {
 	bool result = false;
-	RwMatrix *mat = RwFrameGetMatrix(m_ped->GetNodeFrame(PED_HEAD));
-	if (m_ped->m_pFrames[PED_HEAD]->flag & AnimBlendFrameData::IGNORE_ROTATION) {
-		m_ped->m_pFrames[PED_HEAD]->flag &= (~AnimBlendFrameData::IGNORE_ROTATION);
-	} else {
-		float yaw, pitch;
-		ExtractYawAndPitchLocal(mat, &yaw, &pitch);
-		if (MoveLimb(m_headOrient, yaw, pitch, ms_headRestoreInfo) == ANGLES_SET_EXACTLY)
-			result = true;
+	float yaw, pitch;
+
+#ifdef PED_SKIN
+	if(IsClumpSkinned(m_ped->GetClump())){
+		if (m_ped->m_pFrames[PED_HEAD]->flag & AnimBlendFrameData::IGNORE_ROTATION) {
+			m_ped->m_pFrames[PED_HEAD]->flag &= (~AnimBlendFrameData::IGNORE_ROTATION);
+		} else {
+			ExtractYawAndPitchLocalSkinned(m_ped->m_pFrames[PED_HEAD], &yaw, &pitch);
+			if (MoveLimb(m_headOrient, yaw, pitch, ms_headRestoreInfo) == ANGLES_SET_EXACTLY)
+				result = true;
+		}
+		RotateHead();
+	}else
+#endif
+	{
+		RwMatrix *mat = RwFrameGetMatrix(m_ped->m_pFrames[PED_HEAD]->frame);
+		if (m_ped->m_pFrames[PED_HEAD]->flag & AnimBlendFrameData::IGNORE_ROTATION) {
+			m_ped->m_pFrames[PED_HEAD]->flag &= (~AnimBlendFrameData::IGNORE_ROTATION);
+		} else {
+			ExtractYawAndPitchLocal(mat, &yaw, &pitch);
+			if (MoveLimb(m_headOrient, yaw, pitch, ms_headRestoreInfo) == ANGLES_SET_EXACTLY)
+				result = true;
+		}
+
+		CMatrix matrix(mat);
+		CVector pos = matrix.GetPosition();
+		matrix.SetRotateZ(m_headOrient.theta);
+		matrix.RotateX(m_headOrient.phi);
+		matrix.Translate(pos);
+		matrix.UpdateRW();
 	}
-
-	CMatrix matrix(mat);
-	CVector pos = matrix.GetPosition();
-	matrix.SetRotateZ(m_headOrient.theta);
-	matrix.RotateX(m_headOrient.phi);
-	matrix.Translate(pos);
-	matrix.UpdateRW();
-
-	if (!(m_flags & LOOKAROUND_HEAD_ONLY))
+	if (!(m_flags & LOOKAROUND_HEAD_ONLY)){
 		MoveLimb(m_torsoOrient, 0.0f, 0.0f, ms_torsoInfo);
-	if (!(m_flags & LOOKAROUND_HEAD_ONLY))
-		RotateTorso(m_ped->m_pFrames[PED_MID], &m_torsoOrient, false);
-
+		if (!(m_flags & LOOKAROUND_HEAD_ONLY))
+			RotateTorso(m_ped->m_pFrames[PED_MID], &m_torsoOrient, false);
+	}
 	return result;
 }
 
@@ -362,3 +542,14 @@ CPedIK::ExtractYawAndPitchLocal(RwMatrix *mat, float *yaw, float *pitch)
 	*pitch = Acos(f);
 	if (mat->up.x > 0.0f) *pitch = -*pitch;
 }
+
+#ifdef PED_SKIN
+void
+CPedIK::ExtractYawAndPitchLocalSkinned(AnimBlendFrameData *node, float *yaw, float *pitch)
+{
+	RwMatrix *mat = RwMatrixCreate();
+	RtQuatConvertToMatrix(&node->hanimFrame->q, mat);
+	ExtractYawAndPitchLocal(mat, yaw, pitch);
+	RwMatrixDestroy(mat);
+}
+#endif
diff --git a/src/peds/PedIK.h b/src/peds/PedIK.h
index 7b82d1ac..fd9e4702 100644
--- a/src/peds/PedIK.h
+++ b/src/peds/PedIK.h
@@ -55,9 +55,11 @@ public:
 	static RwMatrix *GetWorldMatrix(RwFrame *source, RwMatrix *destination);
 	void RotateTorso(AnimBlendFrameData* animBlend, LimbOrientation* limb, bool changeRoll);
 	void ExtractYawAndPitchLocal(RwMatrix *mat, float *yaw, float *pitch);
+	void ExtractYawAndPitchLocalSkinned(AnimBlendFrameData *node, float *yaw, float *pitch);
 	void ExtractYawAndPitchWorld(RwMatrix *mat, float *yaw, float *pitch);
 	LimbMoveStatus MoveLimb(LimbOrientation &limb, float approxPhi, float approxTheta, LimbMovementInfo &moveInfo);
 	bool RestoreGunPosn(void);
+	void RotateHead(void);
 	bool LookInDirection(float phi, float theta);
 	bool LookAtPosition(CVector const& pos);
 	bool RestoreLookAt(void);
diff --git a/src/peds/PlayerPed.cpp b/src/peds/PlayerPed.cpp
index 0163c12c..664fff18 100644
--- a/src/peds/PlayerPed.cpp
+++ b/src/peds/PlayerPed.cpp
@@ -1,5 +1,6 @@
 #include "common.h"
 
+#include "RwHelper.h"
 #include "PlayerPed.h"
 #include "Wanted.h"
 #include "Fire.h"
@@ -1497,4 +1498,9 @@ CPlayerPed::ProcessControl(void)
 		m_nSpeedTimer = 0;
 		m_bSpeedTimerFlag = false;
 	}
+
+#ifdef PED_SKIN
+	if (!bIsVisible && IsClumpSkinned(GetClump()))
+		UpdateRpHAnim();
+#endif
 }
diff --git a/src/peds/PlayerPed.h b/src/peds/PlayerPed.h
index 81f8e4d7..b8bd57e4 100644
--- a/src/peds/PlayerPed.h
+++ b/src/peds/PlayerPed.h
@@ -77,4 +77,6 @@ public:
 	static void ReactivatePlayerPed(int32);
 };
 
+#ifndef PED_SKIN
 static_assert(sizeof(CPlayerPed) == 0x5F0, "CPlayerPed: error");
+#endif
diff --git a/src/render/SpecialFX.cpp b/src/render/SpecialFX.cpp
index 16057420..fc081fa3 100644
--- a/src/render/SpecialFX.cpp
+++ b/src/render/SpecialFX.cpp
@@ -117,8 +117,14 @@ CSpecialFX::Update(void)
 
 	if(FindPlayerPed() &&
 	   FindPlayerPed()->GetWeapon()->m_eWeaponType == WEAPONTYPE_BASEBALLBAT &&
-	   FindPlayerPed()->GetWeapon()->m_eWeaponState == WEAPONSTATE_FIRING)
-		RwFrameForAllObjects(FindPlayerPed()->GetNodeFrame(PED_HANDR), LookForBatCB, CModelInfo::GetModelInfo(MI_BASEBALL_BAT));
+	   FindPlayerPed()->GetWeapon()->m_eWeaponState == WEAPONSTATE_FIRING){
+#ifdef PED_SKIN
+		if(IsClumpSkinned(FindPlayerPed()->GetClump())){
+			LookForBatCB((RwObject*)FindPlayerPed()->m_pWeaponModel, CModelInfo::GetModelInfo(MI_BASEBALL_BAT));
+		}else
+#endif
+			RwFrameForAllObjects(FindPlayerPed()->m_pFrames[PED_HANDR]->frame, LookForBatCB, CModelInfo::GetModelInfo(MI_BASEBALL_BAT));
+	}
 }
 
 void
diff --git a/src/rw/RwHelper.cpp b/src/rw/RwHelper.cpp
index acf811ad..5aa4475f 100644
--- a/src/rw/RwHelper.cpp
+++ b/src/rw/RwHelper.cpp
@@ -3,16 +3,15 @@
 
 #include "Timecycle.h"
 #include "skeleton.h"
-#if defined(RWLIBS) && !defined(FINAL)
+#include "Debug.h"
+#ifndef FINAL
 #include "rtcharse.h"
-#pragma comment( lib, "rtcharse.lib" )
-
 RtCharset *debugCharset;
 #endif
 
 void CreateDebugFont()
 {
-#if defined(RWLIBS) && !defined(FINAL)
+#ifndef FINAL
 	RwRGBA color = { 255, 255, 128, 255 };
 	RwRGBA colorbg = { 0, 0, 0, 0 };
 	RtCharsetOpen();
@@ -22,7 +21,7 @@ void CreateDebugFont()
 
 void DestroyDebugFont()
 {
-#if defined(RWLIBS) && !defined(FINAL)
+#ifndef FINAL
 	RtCharsetDestroy(debugCharset);
 	RtCharsetClose();
 #endif
@@ -30,14 +29,14 @@ void DestroyDebugFont()
 
 void ObrsPrintfString(const char *str, short x, short y)
 {
-#if defined(RWLIBS) && !defined(FINAL)
-	RtCharsetPrintBuffered(debugCharset, str, x, y, true);
+#ifndef FINAL
+	RtCharsetPrintBuffered(debugCharset, str, x*8, y*16, true);
 #endif
 }
 
 void FlushObrsPrintfs()
 {
-#if defined(RWLIBS) && !defined(FINAL)
+#ifndef FINAL
 	RtCharsetBufferFlush();
 #endif
 }
@@ -168,6 +167,170 @@ GetFirstTexture(RwTexDictionary *txd)
 	return tex;
 }
 
+#ifdef PED_SKIN
+static RpAtomic*
+isSkinnedCb(RpAtomic *atomic, void *data)
+{
+	RpAtomic **pAtomic = (RpAtomic**)data;
+	if(*pAtomic)
+		return nil;	// already found one
+	if(RpSkinGeometryGetSkin(atomic->geometry))
+		*pAtomic = atomic;	// we could just return nil here directly...
+	return atomic;
+}
+
+RpAtomic*
+IsClumpSkinned(RpClump *clump)
+{
+	RpAtomic *atomic = nil;
+	RpClumpForAllAtomics(clump, isSkinnedCb, &atomic);
+	return atomic;
+}
+
+static RpAtomic*
+GetAnimHierarchyCallback(RpAtomic *atomic, void *data)
+{
+	*(RpHAnimHierarchy**)data = RpSkinAtomicGetHAnimHierarchy(atomic);
+	return nil;
+}
+
+RpHAnimHierarchy*
+GetAnimHierarchyFromSkinClump(RpClump *clump)
+{
+	RpHAnimHierarchy *hier = nil;
+	RpClumpForAllAtomics(clump, GetAnimHierarchyCallback, &hier);
+	return hier;
+}
+
+static RwFrame*
+GetAnimHierarchyFromClumpCB(RwFrame *frame, void *data)
+{
+	RpHAnimHierarchy *hier = RpHAnimFrameGetHierarchy(frame);
+	if(hier){
+		*(RpHAnimHierarchy**)data = hier;
+		return nil;
+	}
+	RwFrameForAllChildren(frame, GetAnimHierarchyFromClumpCB, data);
+	return frame;
+}
+
+RpHAnimHierarchy*
+GetAnimHierarchyFromClump(RpClump *clump)
+{
+	RpHAnimHierarchy *hier = nil;
+	RwFrameForAllChildren(RpClumpGetFrame(clump), GetAnimHierarchyFromClumpCB, &hier);
+	return hier;
+}
+
+RwFrame*
+GetHierarchyFromChildNodesCB(RwFrame *frame, void *data)
+{
+	RpHAnimHierarchy **pHier = (RpHAnimHierarchy**)data;
+	RpHAnimHierarchy *hier = RpHAnimFrameGetHierarchy(frame);
+	if(hier == nil)
+		RwFrameForAllChildren(frame, GetHierarchyFromChildNodesCB, &hier);
+	*pHier = hier;
+	return nil;
+}
+
+void
+SkinGetBonePositionsToTable(RpClump *clump, RwV3d *boneTable)
+{
+	int i, parent;
+	RpAtomic *atomic;
+	RpSkin *skin;
+	RpHAnimHierarchy *hier;
+	int numBones;
+	RwMatrix m, invmat;
+	int stack[32];
+	int sp;
+
+	if(boneTable == nil)
+		return;
+
+//	atomic = GetFirstAtomic(clump);		// mobile, also VC
+	atomic = IsClumpSkinned(clump);		// xbox, seems safer
+	assert(atomic);
+	skin = RpSkinGeometryGetSkin(RpAtomicGetGeometry(atomic));
+	assert(skin);
+	hier = GetAnimHierarchyFromSkinClump(clump);
+	assert(hier);
+	boneTable[0].x = 0.0f;
+	boneTable[0].y = 0.0f;
+	boneTable[0].z = 0.0f;
+	numBones = RpSkinGetNumBones(skin);
+	parent = 0;
+	sp = 0;
+#ifdef FIX_BUGS
+	stack[0] = 0;	// i think this is ok
+#endif
+	for(i = 1; i < numBones; i++){
+		RwMatrixCopy(&m, &RpSkinGetSkinToBoneMatrices(skin)[i]);
+		RwMatrixInvert(&invmat, &m);
+		const RwMatrix *x = RpSkinGetSkinToBoneMatrices(skin);
+		RwV3dTransformPoints(&boneTable[i], &invmat.pos, 1, &x[parent]);
+		if(HIERNODEINFO(hier)[i].flags & rpHANIMPUSHPARENTMATRIX)
+			stack[++sp] = parent;
+		if(HIERNODEINFO(hier)[i].flags & rpHANIMPOPPARENTMATRIX)
+			parent = stack[sp--];
+		else
+			parent = i;
+		assert(parent >= 0 && parent < numBones);
+	}
+}
+
+RpHAnimAnimation*
+HAnimAnimationCreateForHierarchy(RpHAnimHierarchy *hier)
+{
+	int i;
+#ifdef FIX_BUGS
+	int numNodes = hier->numNodes*2;	// you're supposed to have at least two KFs per node
+#else
+	int numNodes = hier->numNodes;
+#endif
+	RpHAnimAnimation *anim = RpHAnimAnimationCreate(rpHANIMSTDKEYFRAMETYPEID, numNodes, 0, 0.0f);
+	if(anim == nil)
+		return nil;
+	RpHAnimStdKeyFrame *frame = (RpHAnimStdKeyFrame*)HANIMFRAMES(anim);
+	for(i = 0; i < numNodes; i++){
+		frame->q.real = 1.0f;
+		frame->q.imag.x = frame->q.imag.y = frame->q.imag.z = 0.0f;
+		frame->t.x = frame->t.y = frame->t.z = 0.0f;
+		frame->time = 0.0f;
+		frame->prevFrame = nil;
+		frame++;
+	}
+	return anim;
+}
+
+void
+RenderSkeleton(RpHAnimHierarchy *hier)
+{
+	int i;
+	int sp;
+	int stack[32];
+	int par;
+	CVector p1, p2;
+	int numNodes = hier->numNodes;
+	RwMatrix *mats = RpHAnimHierarchyGetMatrixArray(hier);
+	p1 = mats[0].pos;
+
+	par = 0;
+	sp = 0;
+	stack[sp++] = par;
+	for(i = 1; i < numNodes; i++){
+		p1 = mats[par].pos;
+		p2 = mats[i].pos;
+		CDebug::AddLine(p1, p2, 0xFFFFFFFF, 0xFFFFFFFF);
+		if(HIERNODEINFO(hier)[i].flags & rpHANIMPUSHPARENTMATRIX)
+			stack[sp++] = par;
+		par = i;
+		if(HIERNODEINFO(hier)[i].flags & rpHANIMPOPPARENTMATRIX)
+			par = stack[--sp];
+	}
+}
+#endif
+
 void
 CameraSize(RwCamera * camera, RwRect * rect,
 		   RwReal viewWindow, RwReal aspectRatio)
diff --git a/src/rw/RwHelper.h b/src/rw/RwHelper.h
index 5b47cb6f..9f178ec2 100644
--- a/src/rw/RwHelper.h
+++ b/src/rw/RwHelper.h
@@ -13,6 +13,15 @@ RwObject *GetFirstObject(RwFrame *frame);
 RpAtomic *GetFirstAtomic(RpClump *clump);
 RwTexture *GetFirstTexture(RwTexDictionary *txd);
 
+#ifdef PED_SKIN
+RpAtomic *IsClumpSkinned(RpClump *clump);
+RpHAnimHierarchy *GetAnimHierarchyFromSkinClump(RpClump *clump);	// get from atomic
+RpHAnimHierarchy *GetAnimHierarchyFromClump(RpClump *clump);	// get from frame
+RwFrame *GetHierarchyFromChildNodesCB(RwFrame *frame, void *data);
+void SkinGetBonePositionsToTable(RpClump *clump, RwV3d *boneTable);
+RpHAnimAnimation *HAnimAnimationCreateForHierarchy(RpHAnimHierarchy *hier);
+#endif
+
 RwTexDictionary *RwTexDictionaryGtaStreamRead(RwStream *stream);
 RwTexDictionary *RwTexDictionaryGtaStreamRead1(RwStream *stream);
 RwTexDictionary *RwTexDictionaryGtaStreamRead2(RwStream *stream, RwTexDictionary *texDict);
diff --git a/src/rw/VisibilityPlugins.cpp b/src/rw/VisibilityPlugins.cpp
index 89bd13a6..7dc27f48 100644
--- a/src/rw/VisibilityPlugins.cpp
+++ b/src/rw/VisibilityPlugins.cpp
@@ -543,6 +543,21 @@ CVisibilityPlugins::RenderPedHiDetailCB(RpAtomic *atomic)
 	return atomic;
 }
 
+// This is needed for peds with only one clump, i.e. skinned models
+// strangely even the xbox version has no such thing
+RpAtomic*
+CVisibilityPlugins::RenderPedCB(RpAtomic *atomic)
+{
+	int32 alpha;
+
+	alpha = GetClumpAlpha(RpAtomicGetClump(atomic));
+	if(alpha == 255)
+		AtomicDefaultRenderCallBack(atomic);
+	else
+		RenderAlphaAtomic(atomic, alpha);
+	return atomic;
+}
+
 float
 CVisibilityPlugins::GetDistanceSquaredFromCamera(RwFrame *frame)
 {
diff --git a/src/rw/VisibilityPlugins.h b/src/rw/VisibilityPlugins.h
index 63bc95e4..b367d7ee 100644
--- a/src/rw/VisibilityPlugins.h
+++ b/src/rw/VisibilityPlugins.h
@@ -63,6 +63,7 @@ public:
 	static RpAtomic *RenderPlayerCB(RpAtomic *atomic);
 	static RpAtomic *RenderPedLowDetailCB(RpAtomic *atomic);
 	static RpAtomic *RenderPedHiDetailCB(RpAtomic *atomic);
+	static RpAtomic *RenderPedCB(RpAtomic *atomic);	// for skinned models with only one clump
 
 	static void RenderAlphaAtomics(void);
 	static void RenderFadingEntities(void);
@@ -131,3 +132,5 @@ public:
 
 	static bool PluginAttach(void);
 };
+
+RpMaterial *SetAlphaCB(RpMaterial *material, void *data);
diff --git a/src/skel/win/win.cpp b/src/skel/win/win.cpp
index d13d3c52..cde9f9e5 100644
--- a/src/skel/win/win.cpp
+++ b/src/skel/win/win.cpp
@@ -1766,6 +1766,14 @@ WinMain(HINSTANCE instance,
 	StaticPatcher::Apply();
 	SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, nil, SPIF_SENDCHANGE);
 
+/*
+	// TODO: make this an option somewhere
+	AllocConsole();
+	freopen("CONIN$", "r", stdin);
+	freopen("CONOUT$", "w", stdout);
+	freopen("CONOUT$", "w", stderr);
+*/
+
 	/* 
 	 * Initialize the platform independent data.
 	 * This will in turn initialize the platform specific data...
diff --git a/src/weapons/Weapon.cpp b/src/weapons/Weapon.cpp
index b7ccb455..54e73a41 100644
--- a/src/weapons/Weapon.cpp
+++ b/src/weapons/Weapon.cpp
@@ -566,8 +566,7 @@ CWeapon::FireInstantHit(CEntity *shooter, CVector *fireSource)
 			target.y = 0.0f;
 			target.z = 0.0f;
 
-			for (RwFrame *i = shooterPed->GetNodeFrame(PED_HANDR); i; i = RwFrameGetParent(i))
-				RwV3dTransformPoints(target, target, 1, RwFrameGetMatrix(i));
+			shooterPed->TransformToNode(target, PED_HANDR);
 
 			ProcessLineOfSight(*fireSource, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, true, false);
 		}