//================================================================
// EQUINOX-3D plugin
//
// Skeletons and inverse kinematics
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Apr-16 01:39:12
//
// Copyright (C) 2014-2026 By Gabor Nagy. All rights reserved.
//================================================================

#include <stdio.h>
#include <math.h>

#include <EMalloc.h>
#include <EPlugins.h>

#include <E3D/E3D.h>
#include <E3D/Matrix.h>

#include <E3D/Rendering.h>

#include <E3D/GL/MeshDraw.h>

#include <E3D/UI/ModeStack.h>    // E3dFIXME : For E3dp_ResetWorkModes, temporarily
#include <E3D/UI/Panel.h>
#include <E3D/UI/StatusPanel.h>  // For E3dp_PrintMessage


#include <Xe/Scale.h>

#include <E3D/UI/Panels.h>

static EPlugin*  _Plugin = NULL;

static XeDWidget  _MenuButtons[16];static int  _MenuButtons_Count = 0;



static char  _ModelName[] = "ChainRoot";


enum
{
	CR_BUILD_SKELETON,
	CR_ADD_JOINTS,
	CR_ASSIGN_SKIN
};


static E3dNode*  _ChainRootNode = NULL;
static E3dNode*  _ChainCurrentNode = NULL;



#define ROffset(field)  (void*)(EPtrOffset(E3dIKEffectorNodeInfo, field))
static EResource  _IKEffectorNodeComponentResources[]=
{
	{ "Iterations",  EResINT32,  ROffset(Iterations) },
};
#undef ROffset


//================================================
//================================================
static int _IKEffectorNodeComponentInit(E3dNode* PNode, E3dNodeComponent* PInfo)
{
	E3dIKEffectorNodeInfo*  LThis = (E3dIKEffectorNodeInfo*)PInfo;

	LThis->Iterations = 5;

	return(E_SUCCESS);
}


//================================================================
// Terminate articulated chain with an effector
//================================================================
static void _AddEffector(E3dNode* PParentNode, E3dNode* PRootNode)
{
	E3dScene* const  LScene = E3d_Scene;
	E3dModel*        LModel;E_New(E3dModel, LModel);if(LModel)
	{
//		E3dIKEffectorNodeInfo*  LInfo = (E3dIKEffectorNodeInfo*)E3dNodeComponent_Add((E3dNode*)LModel, E3dIKEffectorNodeInfo_class);

		E3dNodeComponent_Add((E3dNode*)LModel, E3dIKEffectorNodeInfo_class);

		EObject_SetName(LModel, E3dScene_GetUniqueNodeName(LScene, "Effector"), FALSE);
		LModel->Selection = E3dNodeSelBRANCH;

		E3dNode_ConnectChild(PParentNode, (E3dNode*)LModel, ELstEND, FALSE, FALSE);

		LModel->TransformSet.Translation.X = PParentNode->LocalToWorldMatrix[M30];
		LModel->TransformSet.Translation.Y = PParentNode->LocalToWorldMatrix[M31];
		LModel->TransformSet.Translation.Z = PParentNode->LocalToWorldMatrix[M32];

		E3dNodeHrc_UpdateConnections(PRootNode);
		E3dNodeHrc_UpdateMatrices(PRootNode, TRUE);
		EChangeFlags  LChanged = E3dChgHIERARCHY;LChanged |= E3dNodeHrc_CallNodeInfoUpdateProcs(PRootNode, E3dChgNodeTRANSFORM|E3dChgHIERARCHY, FALSE);

		E3dScene_ObjectsUpdateModelList(LScene);
		E3dScene_LayoutOnSchematics(LScene, TRUE);

		E3dWindows_RedrawByChangeFlags(LScene, LChanged);
	}
}


//================================================================
// Inverse-kinematics solver
//================================================================
static EBool _IKSolve(E3dNode* PChainRoot, E3dNode* PEffector)
{
	E3dIKEffectorNodeInfo*	LEffectorInfo = (E3dIKEffectorNodeInfo*)E3dNodeComponent_ByClass(PEffector, E3dIKEffectorNodeInfo_class, FALSE);if(LEffectorInfo)
	{
		E3dNode*       LNode;
		E3dModel*      LJoint;
		E3dNode*       LChainEnd = PEffector->Parent;
		E3dCoordinate  LMaxDistance = 0.001;
		EIndex         LNumOfBones = 0;


		LNode = LChainEnd;
		while(LNode != PChainRoot)
		{
			LNode = LNode->Parent;
			LNumOfBones++;
		}
		LNumOfBones--;


		if(LNumOfBones < 1) return(FALSE);

//printf("IKSolver  chain root: %s  effector: %s  bones: %d\n", PChainRoot->Name, PEffector->Name, LNumOfBones);fflush(stdout);

// Check the initial distance between the end point of the chain and the effector
//
		const E3dCoordinate  LEffectorX = PEffector->LocalToWorldMatrix[M30];
		const E3dCoordinate  LEffectorY = PEffector->LocalToWorldMatrix[M31];
		const E3dCoordinate  LEffectorZ = PEffector->LocalToWorldMatrix[M32];

		E3dCoordinate        LChainEndX = LChainEnd->LocalToWorldMatrix[M30];
		E3dCoordinate        LChainEndY = LChainEnd->LocalToWorldMatrix[M31];
		E3dCoordinate        LChainEndZ = LChainEnd->LocalToWorldMatrix[M32];

		E3dCoordinate        LDX = LEffectorX - LChainEndX;
		E3dCoordinate        LDY = LEffectorY - LChainEndY;
		E3dCoordinate        LDZ = LEffectorZ - LChainEndZ;
		E3dCoordinate        LDistance = E3d_Sqrt(LDX * LDX + LDY * LDY + LDZ * LDZ);


// If the distance is smaller than LMaxDistance, we already have a solution
//
		if(LDistance < LMaxDistance) return(TRUE);

//printf(" distance %f\n", LDistance);fflush(stdout);

// Cyclic Coordinate Descent
//
		{
			E3dMatrix*         LAlignMatrices;ENewN(LNumOfBones, LAlignMatrices);
			E3dJointNodeInfo*  LJointNodeInfo;
			E3dMatrix          LAlignMatrix;
			E3dMatrix          LWorldToLocalMatrix;
			E3dCoordinate      LBoneStartX, LBoneStartY, LBoneStartZ,
			                   LInvNumOfBones = 1.0 / (E3dCoordinate)LNumOfBones,
			                   LXWeight, LYWeight, LZWeight,
			                   LMaxFriction, LMax;
			EIndex             LIterations = 0, LNumOfIterations = 1;


// Reset the AlignMatrices
//
			{
				EIndex  LC = 0;for(;LC < LNumOfBones;LC++)
				{
					E3dMatrix_LoadIdentity(LAlignMatrices[LC]);
				}
			}


// The LNumOfIterations will be 1.0 / highest friction
//
			LJoint = (E3dModel*)(LChainEnd->Parent);
			LJointNodeInfo = (E3dJointNodeInfo*)E3dNodeComponent_ByClass((E3dNode*)LJoint, E3dJointNodeInfo_class, FALSE);

			LMax = LJointNodeInfo->AngularDampening.X;
			if(LJointNodeInfo->AngularDampening.Y > LMax) LMax = LJointNodeInfo->AngularDampening.Y;
			if(LJointNodeInfo->AngularDampening.Z > LMax) LMax = LJointNodeInfo->AngularDampening.Z;
			LMaxFriction = LMax;

			LJoint = (E3dModel*)(LJoint->Parent);
			LJointNodeInfo = (E3dJointNodeInfo*)E3dNodeComponent_ByClass((E3dNode*)LJoint, E3dJointNodeInfo_class, FALSE);

			{
				EIndex  LC = 1;for(;LC < LNumOfBones;LC++)
				{
					LMax = LJointNodeInfo->AngularDampening.X;
					if(LJointNodeInfo->AngularDampening.Y > LMax) LMax = LJointNodeInfo->AngularDampening.Y;
					if(LJointNodeInfo->AngularDampening.Z > LMax) LMax = LJointNodeInfo->AngularDampening.Z;
					if(LMax > LMaxFriction) LMaxFriction = LMax;

					LJoint = (E3dModel*)(LJoint->Parent);
				}
			}

//		LNumOfIterations = (LMaxFriction * 2.0 + 1.0);
			LNumOfIterations = LEffectorInfo->Iterations;


			{
//u_long64  LCyclesStart, LCyclesEnd;


//LCyclesStart = _x86_GetCycleCount();


//printf("LMaxFriction %f, LNumOfIterations %d\n", LMaxFriction, LNumOfIterations);fflush(stdout);

// We'll only use the top-left 3x3 portion, so we can reset it here
//
				E3dMatrix_LoadIdentity(LWorldToLocalMatrix);

				while(1)
				{
					LJoint = (E3dModel*)(LChainEnd->Parent);
					LJointNodeInfo = (E3dJointNodeInfo*)E3dNodeComponent_ByClass((E3dNode*)LJoint, E3dJointNodeInfo_class, FALSE);
					EIndex  LC = 0;for(;LC < LNumOfBones;LC++)
					{

// To avoid lag, we only apply friction on the joints in the first n-3 iterations
//
						if(1)
//    if(LIterations < (LNumOfIterations-2))
						{
							LXWeight = LInvNumOfBones/(LJointNodeInfo->AngularDampening.X + 1.0);
							LYWeight = LInvNumOfBones/(LJointNodeInfo->AngularDampening.Y + 1.0);
							LZWeight = LInvNumOfBones/(LJointNodeInfo->AngularDampening.Z + 1.0);
						}
						else
						{
							LXWeight = LInvNumOfBones;
							LYWeight = LInvNumOfBones;
							LZWeight = LInvNumOfBones;
						}

						LBoneStartX = LJoint->LocalToWorldMatrix[M30];
						LBoneStartY = LJoint->LocalToWorldMatrix[M31];
						LBoneStartZ = LJoint->LocalToWorldMatrix[M32];



						E3dMatrix_Invert3x3(LJoint->LocalToWorldMatrix, LWorldToLocalMatrix);
//E3dMatrix_Print4x4(LWorldToLocalMatrix);
//printf("\n");fflush(stdout);

// We will convert the Bone start to chain end unit-vector and the Bone start to Effector unit-vector
// into Joint-local space, so the reqired rotation will be in Joint-local space right away
//

// Bone start to chain end unit-vector in Joint-local space
//
						E3dCoord3  LSEnd;
						EMatrix_Transform3x3_sv(LWorldToLocalMatrix,  LChainEndX - LBoneStartX, LChainEndY - LBoneStartY, LChainEndZ - LBoneStartZ,  LSEnd);
						E3d_Normalize3_v(LSEnd);


//printf("BoneStart %f,%f,%f  ChEnd %f,%f,%f\n", LBoneStartX, LBoneStartY, LBoneStartZ, LChainEndX, LChainEndY, LChainEndZ);fflush(stdout);


// Bone start to Effector unit-vector in Joint-local space
//
						E3dCoord3	LD;
						EMatrix_Transform3x3_sv(LWorldToLocalMatrix,  LEffectorX - LBoneStartX, LEffectorY - LBoneStartY, LEffectorZ - LBoneStartZ,  LD);
						E3d_Normalize3_v(LD);

// Using a quaternion, compute the Matrix that aligns the LSEnd vector with the LD vector
//
						E3d_VectorsGetRotMatrix(&LSEnd, &LD, LAlignMatrix);


						E3dCoord3  LEulerAngles;E3dMatrix_ToEuler(LAlignMatrix, E3dZYX, &LEulerAngles);

						E3dTransformSet*  LTransformSet = &LJoint->TransformSet;
//printf("Rotation: %f,%f,%f\n", LRotation.Angles.X, LRotation.Angles.Y, LRotation.Angles.Z);fflush(stdout);
						LTransformSet->EulerRotation.Angles.X += LEulerAngles.X * LXWeight;
						LTransformSet->EulerRotation.Angles.Y += LEulerAngles.Y * LYWeight;
						LTransformSet->EulerRotation.Angles.Z += LEulerAngles.Z * LZWeight;

// Clamp the angles to 0.0...360.0
//
						E3dEulerRotation*	LJointRotation = &LJoint->TransformSet.EulerRotation;
						if(LJointRotation->Angles.X > 180.0) LJointRotation->Angles.X -= 360.0;
						else if(LJointRotation->Angles.X < -180.0)LJointRotation->Angles.X += 360.0;

						if(LJointRotation->Angles.Y > 180.0) LJointRotation->Angles.Y -= 360.0;
						else if(LJointRotation->Angles.Y < -180.0) LJointRotation->Angles.Y += 360.0;

						if(LJointRotation->Angles.Z > 180.0)LJointRotation->Angles.Z -= 360.0;
						else if(LJointRotation->Angles.Z < -180.0)LJointRotation->Angles.Z += 360.0;

// Check for the angle limits
//
						if(LJointRotation->Angles.X < LJointNodeInfo->AngleXMin) LJointRotation->Angles.X = LJointNodeInfo->AngleXMin;
						else if(LJointRotation->Angles.X > LJointNodeInfo->AngleXMax) LJointRotation->Angles.X = LJointNodeInfo->AngleXMax;

						if(LJointRotation->Angles.Y < LJointNodeInfo->AngleYMin) LJointRotation->Angles.Y = LJointNodeInfo->AngleYMin;
						else if(LJointRotation->Angles.Y > LJointNodeInfo->AngleYMax) LJointRotation->Angles.Y = LJointNodeInfo->AngleYMax;

						if(LJointRotation->Angles.Z < LJointNodeInfo->AngleZMin) LJointRotation->Angles.Z = LJointNodeInfo->AngleZMin;
						else if(LJointRotation->Angles.Z > LJointNodeInfo->AngleZMax) LJointRotation->Angles.Z = LJointNodeInfo->AngleZMax;



//printf(" %f,%f,%f  %f,%f,%f\n",  LSEndX, LSEndY, LSEndZ,  LDX, LDY, LDZ);fflush(stdout);


						E3dNodeHrc_UpdateMatricesNoScaling(PChainRoot);
//						E3dNodeHrc_CallNodeInfoUpdateProcs(PChainRoot, E3dChgNodeTRANSFORM, FALSE);

						LJoint = (E3dModel*)(LJoint->Parent);
					}

//printf("\n");fflush(stdout);

					LChainEndX = LChainEnd->LocalToWorldMatrix[M30];
					LChainEndY = LChainEnd->LocalToWorldMatrix[M31];
					LChainEndZ = LChainEnd->LocalToWorldMatrix[M32];


					LDX = LEffectorX - LChainEndX;
					LDY = LEffectorY - LChainEndY;
					LDZ = LEffectorZ - LChainEndZ;
					LDistance = E3d_Sqrt(LDX * LDX + LDY * LDY + LDZ * LDZ);

// If the distance is smaller than LMaxDistance, we have a solution
//
					if(LDistance < LMaxDistance)
					{
//LCyclesEnd = _x86_GetCycleCount();
//printf("IK TRUE CPU Cycles %lld\n", LCyclesEnd - LCyclesStart);fflush(stdout);
						if(LAlignMatrices) EFree(LAlignMatrices);
						return(TRUE);
					}

					LIterations++;

					if(LIterations > LNumOfIterations)
					{
//LCyclesEnd = _x86_GetCycleCount();
//printf("IK FALSE CPU Cycles %lld\n", LCyclesEnd - LCyclesStart);fflush(stdout);
						if(LAlignMatrices) EFree(LAlignMatrices);
						return(FALSE);
					}
				}

//LCyclesEnd = _x86_GetCycleCount();
//printf("IK End CPU Cycles %lld\n", LCyclesEnd - LCyclesStart);fflush(stdout);
			}

			if(LAlignMatrices) EFree(LAlignMatrices);
		}
	}

	return(TRUE);
}


//================================================================
// Pick callback handler
//================================================================
static void _PickCB(E3dPickCallbackStruct* PPickStruct, EPointer PClientData,  const EIndex PNumOfItems, E3dItem* PPickedItems)
{
	E3dScene*  LScene = E3d_Scene;
	E3dModel*  LModel;
	E3dModel*  LCameraModel = PPickStruct->CameraModel;
	E3dCoord3  L;
	EBool      LChanged = FALSE;(void)LChanged;


	if(PPickStruct->Reason == E3dCR_ABORT)
	{
		if(_ChainCurrentNode) _AddEffector(_ChainCurrentNode, _ChainRootNode);

		_ChainRootNode = NULL;
		_ChainCurrentNode = NULL;

		return;
	}

	if(PPickStruct->Button == Button3)
	{
		if(_ChainCurrentNode) _AddEffector(_ChainCurrentNode, _ChainRootNode);

		_ChainRootNode = NULL;
		_ChainCurrentNode = NULL;

		E3dp_PickEnd();

		return;
	}

	if(PPickStruct->Button == Button2)
	{
		if(_ChainCurrentNode) _AddEffector(_ChainCurrentNode, _ChainRootNode);

		_ChainRootNode = NULL;
		_ChainCurrentNode = NULL;

		return;
	}


	{
		E3dPlane   LPlane;
		E3dModel*  LJointNode;
		E3dRay     LRay;


// Intersect the ray cast by the pick algorithm with a plane that is perpendicular
// to the Camera's view direction and passes through the Camera's interest point
//
		{
			LRay.Origin    = PPickStruct->RayOrigin;
			LRay.Direction = PPickStruct->RayDirection;

// If there is a _ChainCurrentNode, get the plane that goes through its position...
//
			if(_ChainCurrentNode)
			{
			 E3dCamera_GetViewPlaneThroughPoint(LCameraModel, _ChainCurrentNode->LocalToWorldMatrix[M30], _ChainCurrentNode->LocalToWorldMatrix[M31], _ChainCurrentNode->LocalToWorldMatrix[M32], &LPlane);
			}
			else
			{
// ...otherwise, get the plane that goes through the Camera's interest point
//
			 E3dCamera_GetViewPlaneThroughPoint(LCameraModel, 0.0, 0.0, 0.0, &LPlane);
			}


// t is the parameter for the ray where it intersects the plane
//
			const E3dCoordinate	LT = (LPlane.D - LPlane.A * LRay.Origin.X - LPlane.B * LRay.Origin.Y - LPlane.C * LRay.Origin.Z) / (LPlane.A * LRay.Direction.X + LPlane.B * LRay.Direction.Y + LPlane.C * LRay.Direction.Z);

			L.X = LRay.Origin.X + LT * LRay.Direction.X;
			L.Y = LRay.Origin.Y + LT * LRay.Direction.Y;
			L.Z = LRay.Origin.Z + LT * LRay.Direction.Z;

			if(PPickStruct->SnapToX) L.X = E3d_SnapCoordinateTo(L.X, PPickStruct->SnapToXV);
			if(PPickStruct->SnapToY) L.Y = E3d_SnapCoordinateTo(L.Y, PPickStruct->SnapToYV);
			if(PPickStruct->SnapToZ) L.Z = E3d_SnapCoordinateTo(L.Z, PPickStruct->SnapToZV);
		}

		switch(PPickStruct->Button)
		{
			case Button1:
				LChanged = TRUE;

				if(_ChainRootNode == NULL)
				{
					E_New(E3dModel, LModel);
					if(LModel)
					{
						EObject_SetName(LModel, E3dScene_GetUniqueNodeName(LScene, _ModelName), FALSE);
						LModel->TransformSet.Translation = L;

						LModel->Selection = E3dNodeSelBRANCH_ROOT;
						_ChainRootNode = (E3dNode*)LModel;
						_ChainCurrentNode = (E3dNode*)LModel;

						E_New(E3dModel, LJointNode);
						if(LJointNode)
						{
							E3dNodeComponent_Add((E3dNode*)LJointNode, E3dJointNodeInfo_class);

							EObject_SetName(LJointNode, E3dScene_GetUniqueNodeName(LScene, "Joint"), FALSE);
							LJointNode->TransformSet.Translation.X = 0.0;
							LJointNode->TransformSet.Translation.Y = 0.0;
							LJointNode->TransformSet.Translation.Z = 0.0;

							E3dNode_ConnectChild(_ChainCurrentNode, (E3dNode*)LJointNode, ELstEND, FALSE, FALSE);

							LJointNode->Selection = E3dNodeSelBRANCH;
							_ChainCurrentNode = (E3dNode*)LJointNode;
						}

						E3dNodeHrc_UpdateConnections(_ChainRootNode);
						E3dNodeHrc_UpdateMatrices(_ChainRootNode, TRUE);
						EChangeFlags  LSceneChangedFlags = E3dChgHIERARCHY;LChanged |= E3dNodeHrc_CallNodeInfoUpdateProcs(_ChainRootNode, E3dChgHIERARCHY, FALSE);
						E3dScene_NodeHrcAdd(LScene, _ChainRootNode, E3dCallSCENE_CALLBACKS);

						E3dScene_ObjectsUpdateModelList(LScene);
						E3dScene_LayoutOnSchematics(LScene, TRUE);
						E3dWindows_RedrawByChangeFlags(LScene, LSceneChangedFlags);
					}
				}
				else
				{
					E_New(E3dModel, LJointNode);

					LJointNode->TransformSet.EulerRotation.Order = E3dZYX;
					if(LJointNode)
					{
//					E3dJointNodeInfo*  LJointNodeInfo = (E3dJointNodeInfo*)E3dNodeComponent_Add((E3dNode*)LJointNode, E3dJointNodeInfo_class);
						E3dMatrix          LParentWorldToLocalMatrix;
						E3dCoord3          LZAxis;


						E3dNodeComponent_Add((E3dNode*)LJointNode, E3dJointNodeInfo_class);

						EObject_SetName(LJointNode, E3dScene_GetUniqueNodeName(LScene, "Joint"), FALSE);

// Transform the position into the _ChainCurrentNode's local coordinate system
//
						E3dMatrix_LoadIdentity(LParentWorldToLocalMatrix);
						E3dMatrix_Invert3x4(_ChainCurrentNode->LocalToWorldMatrix, LParentWorldToLocalMatrix);

						EMatrix_Transform3x4_v(LParentWorldToLocalMatrix,  L);

						EMatrix_Transform3x3_vv(LParentWorldToLocalMatrix, LRay.Direction,  LZAxis);

						E3dModel_OrientBone(LJointNode, _ChainCurrentNode,  L.X, L.Y, L.Z,  LZAxis.X, LZAxis.Y, LZAxis.Z);


						EChangeFlags  LSceneChangedFlags = E3dChgHIERARCHY;

						E3dNode_ConnectChild(_ChainCurrentNode, (E3dNode*)LJointNode, ELstEND, FALSE, FALSE);

						{
							E3dNode*  LNode = (E3dNode*)LJointNode;

							while(LNode->Parent) LNode = LNode->Parent;
							E3dNodeHrc_UpdateConnections(LNode);
							E3dNodeHrc_UpdateMatrices(LNode, TRUE);
							LSceneChangedFlags |= E3dNodeHrc_CallNodeInfoUpdateProcs(LNode, E3dChgHIERARCHY, FALSE);
						}

						E3dScene_ObjectsUpdateModelList(LScene);
						E3dScene_LayoutOnSchematics(LScene, TRUE);

						LJointNode->Selection = E3dNodeSelBRANCH;
						_ChainCurrentNode = (E3dNode*)LJointNode;

						E3dWindows_RedrawByChangeFlags(LScene, LSceneChangedFlags);
					}
				}
			break;
		}
	}
}


static E3dNode* _GetSelectedSkeletonRoot(E3dScene* PScene)
{
	E3dNode*  LSkeletonRoot = NULL;

 printf("%s() %s:%d %sIMPLEMENT ME%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal);fflush(stdout);
/*
	EIndex  LC = 0;for(;LC < PScene->RootNodes_Count;LC++)
	{
	 E3dNode*  LNode = PScene->RootNodes[LC];
	}
*/

	return(LSkeletonRoot);
}


//================================================================
// Pick callback handler
//================================================================
static void _PickCB_Skin(E3dPickCallbackStruct* PPickStruct, EPointer PClientData,  const EIndex PNumOfItems, E3dItem* PPickedItems)
{
	if(PPickStruct->Reason == E3dCR_ABORT) return;

	switch(PPickStruct->Button)
	{
		case Button1:
			{
printf("%s()\n", __FUNCTION__);fflush(stdout);
				E3dp_PickEnd();
			}
		break;

		case Button3:
			E3dp_PickEnd();
		return;
	}
}


//========================================
// Menu callback
//========================================
static void _MCB_Skeleton(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dp_ResetWorkModes();

	const E3dpPrefs*  LPrefs = &E3dp_Prefs;
	const uint32_t    LLabelRGBAp8Color = Xe_ForegroundRGBA8pColor;

	switch((EPointerInt)PClientData)
	{
		case CR_BUILD_SKELETON:
			E3dp_PickRequest(XeString_NewVa(TRUE,
			 XeFONT, XeFontDEFAULT,  XeCOLOR_RGBA, LLabelRGBAp8Color,  XeTEXT, "IK : create chain  ",
			 E3dp_LMouseButtonS,     XeTEXT, ": add joint  ",
			 E3dp_RMouseButtonEscS,  XeTEXT, ": end and add effector",
			 0), E3dItemWORLD_POSITION,  _PickCB, NULL,  0, FALSE);
		break;

		case CR_ADD_JOINTS:
		break;

		case CR_ASSIGN_SKIN:
			{
				E3dNode*  LSkeletonRoot = _GetSelectedSkeletonRoot(E3d_Scene);if(LSkeletonRoot)
				{
					E3dp_PickRequest(XeString_NewVa(TRUE,
					 XeFONT, XeFontDEFAULT,  XeCOLOR_RGBA, LLabelRGBAp8Color,  XeTEXT, "Assign skin to skeleton:  ",
					 E3dp_LMouseButtonS,     XeTEXT, ": pick skin   ",
					 E3dp_RMouseButtonEscS,  XeTEXT, ": cancel",
					 0),
					 E3dItemMESH,  _PickCB_Skin, NULL,  0, FALSE);
				}
				else E3dp_PrintCompoundMessageVa(ELogWARNING, 5000,  XeFONT, XeFontDEFAULT,  XeTEXT, "Select a skeleton",  0);
			}
		break;
	}
}


//========================================
// Scene changed callback
//========================================
static void _CB_Scene(ECoreObject* PScn, EPointer PClientData, ECallbackStruct* PCallData)
{
	switch(PCallData->Reason)
	{
		case E3dCR_NODE_TRANSLATE:
			{
				E3dSceneNodesCallbackStruct*  LCBS = (E3dSceneNodesCallbackStruct*)PCallData;
				E3dNode**                     LNodes = LCBS->Nodes;
				EListAI(, E3dNode*, LRootNodes);

// Collect list of hierarchies with changed Nodes in them
//
				{
					EIndex  LC = 0;const EIndex  LNumOfNodes = LCBS->NumOfNodes;for(;LC < LNumOfNodes;LC++)
					{
						E3dNode*  LRoot = LNodes[LC];while(LRoot->Parent) LRoot = LRoot->Parent;
						E3dNode*  LNode = LRoot;EBool  LHierarchyHasJoint = FALSE;for(;LNode;LNode = LNode->Next)
						{
							if(E3dNodeComponent_ByClass(LNode, E3dJointNodeInfo_class, FALSE)) { LHierarchyHasJoint = TRUE;break; }
						}
						if(LHierarchyHasJoint) EList_PointerAddAChk(LRootNodes, 64, LRoot);
					}
				}


				{
					EIndex  LRC = 0;const EIndex  LRN = LRootNodes_Count;for(;LRC < LRN;LRC++) E3dNodeHrc_UpdateMatrices(LRootNodes[LRC], FALSE);
				}

				{
					EIndex  LC = 0;const EIndex  LNumOfNodes = LCBS->NumOfNodes;for(;LC < LNumOfNodes;LC++)
					{
						E3dModel*          LJoint = (E3dModel*)(LNodes[LC]);
						E3dJointNodeInfo*  LJointNodeInfo = (E3dJointNodeInfo*)E3dNodeComponent_ByClass((E3dNode*)LJoint, E3dJointNodeInfo_class, FALSE);
						if(LJointNodeInfo && LJoint->Parent)
						{
							E3dModel*  LParentModel = E_DynamicCast(LJoint->Parent, E3dModel);if(LParentModel)
							{
								E3dJointNodeInfo*  LParentJointNodeInfo = (E3dJointNodeInfo*)E3dNodeComponent_ByClass((E3dNode*)LParentModel, E3dJointNodeInfo_class, FALSE);if(LParentJointNodeInfo)
								{
									E3dMatrix  LMatrix;


// By moving an end joint, we are resetting the default orientation
// of its bone, so we need to reset the rotation too
//
									E3dTransformSet_RotationInit(&LParentModel->TransformSet);

									E3dMatrix_LoadIdentity(LMatrix);
									E3dMatrix_Invert3x4(LParentJointNodeInfo->LocalToWorldMatrixNoOrient, LMatrix);

//printf("%s() %s:%d [%s]\n", __FUNCTION__, __FILE__, __LINE__, LJoint->Name);fflush(stdout);
// World position of LNode
//
									E3dCoord3  L;
									L.X = LJoint->LocalToWorldMatrix[M30];
									L.Y = LJoint->LocalToWorldMatrix[M31];
									L.Z = LJoint->LocalToWorldMatrix[M32];
									EMatrix_Transform3x4_v(LMatrix,  L);

									E3dCoord3	LZAxis;EMatrix_Transform3x3_sv(LMatrix,  0.0, 0.0, -1.0,  LZAxis);


									E3dModel_OrientBone(LJoint, (E3dNode*)LParentModel,  L.X, L.Y, L.Z,  LZAxis.X, LZAxis.Y, LZAxis.Z);
							 }
							}
						}
					}
				}

				{
					EIndex  LRC = 0;const EIndex  LRN = LRootNodes_Count;for(;LRC < LRN;LRC++)
					{
						E3dNodeHrc_UpdateMatrices(LRootNodes[LRC], FALSE);
//						E3dNodeHrc_CallNodeInfoUpdateProcs(LRoot, E3dChgNodeTRANSFORM, FALSE);
					}
				}
			}
		break;
	}
}


//================================================================================
// Call the "Update" procedures of the Models in the given branch
//================================================================================
static EChangeFlags _ChainCallNodeInfoUpdateProcs(E3dNode* PRootNode, E3dNode* PEffector, const EChangeFlags PChangeFlags)
{
	EChangeFlags  LChanged = 0;
	E3dNode*      LNextNodeAfterBranch = E3dNode_BranchGetNodeAfter(PRootNode);

	E3dNode*  LNode = PRootNode;for(;LNode != LNextNodeAfterBranch;LNode = LNode->Next)
	{
		if(LNode != PEffector && LNode->Components)
		{
			EIndex  LC = 0;const EIndex  LN = LNode->Components_Count;for(;LC < LN;LC++)
			{
				E3dNodeComponent*       LInfo = LNode->Components[LC];
				E3dNodeComponentClass*  LClass = LInfo->Class;if(LClass)
				{
					if(LClass->Update) LChanged |= LClass->Update(PRootNode, (E3dNode*)LNode, LInfo, PChangeFlags, FALSE);
				}
			}
		}
	}

	return(LChanged);
}


static EChangeFlags _IKEffectorUpdate(E3dNode* PChangedBranchRootNode, E3dNode* LNode, E3dNodeComponent* PNodeInfo, const EChangeFlags PChanged, const EBool PCheckOnly)
{
//	if(PCheckOnly) return(0);

	if(PChanged == 0) return(0);

//printf("%s() %s:%d  Branch [%s]  Node [%s]  %08x\n", __FUNCTION__, __FILE__, __LINE__, PChangedBranchRootNode->Name, LNode->Name, PChanged);fflush(stdout);
//assert(PChanged != 2);
	EChangeFlags	LTriggeredChanges = 0;

	if((PChanged & E3dUpdateNodeTRANSFORM) && PChangedBranchRootNode == LNode)
	{
//printf("%s() %s:%d  Branch [%s]  Node [%s]\n", __FUNCTION__, __FILE__, __LINE__, PChangedBranchRootNode->Name, LNode->Name);fflush(stdout);
		if(E3dNodeComponent_ByClass(LNode->Parent, E3dJointNodeInfo_class, FALSE))
		{
			E3dNode*	LChainRoot = LNode->Parent;

// The root of the IK chain will be the first non-joint node
//
			while(LChainRoot->Parent)
			{
				if(E3dNodeComponent_ByClass(LChainRoot, E3dJointNodeInfo_class, FALSE) == NULL) break;
				LChainRoot = LChainRoot->Parent;
			}
			_IKSolve(LChainRoot, LNode);


			LTriggeredChanges |= _ChainCallNodeInfoUpdateProcs(LChainRoot, LNode, E3dUpdateNodeTRANSFORM);
			LTriggeredChanges |= E3dChgWIREFRAME|E3dChgNodeTRANSFORM;	// To force redrawing skeletons

/*
// Set UpdateCnt on affected skins
//
			{
				E3dNode*	LBranchNext = E3dNode_BranchGetNodeAfter(LChainRoot);
				E3dNode*	LTNode = LChainRoot;for(;LTNode != LBranchNext;LTNode = LTNode->Next)
				{
					if(E3dNodeComponent_ByClass(LTNode, E3dJointNodeInfo_class, FALSE))
					{
						if(LTNode->Skins)
						{
@*
E3dFIXME
							E3dMesh*       LMesh;
							E3dJointNode*  LJoint = (E3dJointNode*)LTNode;E3d_JointNodeUpdateSkinningMatrix(LJoint);

							if(EObject_IsSubClass(LJoint->Skin, E3dMesh))
							{
								E3dSkinMeshInfo*  LSkin = (E3dSkinMeshInfo*)E3dObjectComponent_ByClass((E3dObject*)LJoint->Skin, E3dSkinMeshInfo_class, FALSE);

								LSkin->UpdateCnt += 1;
							}
@/
						}
					}
				}
			}
*/
		}
	}

	return(LTriggeredChanges);
}


//========================================
// NodePanel callback
//========================================
static void _DCB_Iterations(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*           LPanel = (E3dNodePanel*)PClientData;
	E3dNode*                LNode = LPanel->Node;
	E3dIKEffectorNodeInfo*  LInfo = (E3dIKEffectorNodeInfo*)E3dNodeComponent_ByClass(LNode, E3dIKEffectorNodeInfo_class, FALSE);

	LInfo->Iterations = ((XeScaleCallbackStruct*)PCallData)->Value.i32;
}


typedef struct
{
	XeWidget  IKSolverIterationsW;
} E3dIKEffectorResourcesGUI;


static void _IKEffectorGUI(E3dNode* PNode, E3dNodeComponent* PInfo, XeInfoPanel* PPanel, void* PDrawer, const int PAction)
{
	E3dNodePanel*               LPanel = (E3dNodePanel*)PPanel;
	XeComponentClassDrawer*     LDrawer = (XeComponentClassDrawer*)PDrawer;
	E3dIKEffectorNodeInfo*      LInfo = (E3dIKEffectorNodeInfo*)PInfo;
	E3dIKEffectorResourcesGUI*  LResourcesGUI;

	switch(PAction)
	{
		case E_UPDATE:
			if(LInfo)
			{
				if(LDrawer->ResourcesGUI)
				{
					LResourcesGUI = (E3dIKEffectorResourcesGUI*)(LDrawer->ResourcesGUI);

					XeScale_SetValue(LResourcesGUI->IKSolverIterationsW, LInfo->Iterations, FALSE);
				}
				else
				{
					XeWidget  LW;
					XeWidget  LIndentWs[16];int  LIndentN;      // For indenting the ScaleWidgets
					XeWidget  LTraverseWs[16];int  LTraverseN;  // For TextField traversal
					EArg      Args[32];int  ArgCnt;

					ENew(LResourcesGUI);LDrawer->ResourcesGUI = LResourcesGUI;

					LIndentN = 0;
					LTraverseN = 0;
					EXeStart;
					EXeSetArg(XeNScaleLengthInPixels, 120);
					EXeSetArg(XeNTextFieldDigits, 4);EXeSetArg(XeNValue, LInfo->Iterations);
					EXeSetArg(XeNMinimum, 1);EXeSetArg(XeNMaximum, 50);
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->IKSolverIterationsW = LW = XeWidget_ManagedNew("Iterations", XeScaleWidget_class, LDrawer->Drawer.DrawerW, Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_Iterations, (EPointer)LPanel);

					XeWidgets_IndentFromLeft(LIndentWs, LIndentN, 4);
					XeTraversal_SetRing(LTraverseWs, LTraverseN);
				}
			}
		break;

		case E_FREE:
		break;
	}
}


static E3dHWDrawFlags _IKEffectorGLDraw(void* P3DWin, const E3dDrawContext* PDrawContext,  E3dNode* PNode, E3dNodeComponent* PInfo)
{
	const EcRGBAf32Color*  LColor;

	if(PNode->Selection != E3dNodeSelNONE) LColor = &PDrawContext->HWireRGBAfColor;
	else LColor = &PDrawContext->WireRGBAfColor;

	E3dGPUContext*  LGPUContext = ((E3dWindow*)P3DWin)->GPUContext;
	E3dMesh_DrawEdges(LGPUContext, E3d_3DCrossMesh,  LColor, NULL);

	return(0);
}


//========================================
// Entry point of the plugin
//========================================
int E_PluginInit(EPlugin* PPlugin)
{
	_Plugin = PPlugin;


// Register the "IK effector" NodeComponentClass
// Note: The GUIProc will be defined in the Edit plugin
//
	{
		E3dNodeComponentClass  LEffectorNodeClass=
		{
			.Name=         "Inverse-kinematics effector",
			.StructSize=   sizeof(E3dIKEffectorNodeInfo),
			.Resources=    EListS_s(_IKEffectorNodeComponentResources),
			.Init=         _IKEffectorNodeComponentInit,

			.Update=       _IKEffectorUpdate,
			.GLDraw=       _IKEffectorGLDraw,
			.GUI=          _IKEffectorGUI
		};
		E3dIKEffectorNodeInfo_class = E3dNodeComponent_ClassRegister(&LEffectorNodeClass);
	}

	E3dScene_CallbackAdd(E3d_Scene, E3dChgNodeTRANSFORM, _CB_Scene, (EPointer)0);

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Constraints", "Create IK chain", '\0', "~Shift ~Ctrl <Key>K", "K", FALSE, NULL, _MCB_Skeleton, (EPointer)CR_BUILD_SKELETON);
//	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Constraints", "Add joint", '\0', "~Shift ~Ctrl <Key>K", "J", FALSE, NULL, _MCB_Skeleton, (EPointer)CR_ADD_JOINTS);
//	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Constraints", "Assign skin", '\0', NULL, NULL, FALSE, NULL, _MCB_Skeleton, (EPointer)CR_ASSIGN_SKIN);

	return(E_SUCCESS);
}


//========================================
// Exit method of the plugin
//========================================
int E_PluginExit(const EBool PExiting)
{
	if(!PExiting || Xe_GUIFreeOnExit)
	{
		int  LC = 0;for(;LC < _MenuButtons_Count;LC++) { XeItemFree(_MenuButtons[LC]); } _MenuButtons_Count = 0;
	}

	E3dScene_CallbackRemove(E3d_Scene, _CB_Scene, (EPointer)0);
	E3dNodeComponent_ClassDeactivate(E3dIKEffectorNodeInfo_class);

	return(E_SUCCESS);
}
