//================================================================================================
// Plugin for EQUINOX-3D
//
// Example to show how to create custom Node.
// This plugin demonstrates Snell's law of refraction.
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Feb-15 01:33:11
//
// Copyright (C) 2014-2026 by Gabor Nagy. All rights reserved.
//================================================================================================
#include <assert.h>
#include <stdio.h>

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

#include <E3D/Scene.h>

#include <E3D/UI/Panel.h>
#include <E3D/UI/Panels.h>

#include <Xe/PushButton.h>
#include <Xe/Scale.h>


char*            E_PluginInfo = "Demonstrates Snell's law of refraction";

static EPlugin*  _Plugin = NULL;

static XeDWidget  _ToolBarButtons[16];static int  _ToolBarButtons_Count = 0;


typedef struct
{
	E3dNodeComponentCore;

// E3dComponent part
//
	E3dCoordinate  RefractiveIndexTop, RefractiveIndexBottom;
	E3dCoordinate  AngleTop, AngleBottom;

	EBool          SplinesCreated;
} E3dComponent;


#define ROffset(field)  (void*)(EPtrOffset(E3dComponent, field))
static EResource  _Resources[]=
{
	{ "RefractiveIndexTop",     E3dCOORDINATE_TYPE,  ROffset(RefractiveIndexTop) },
	{ "AngleTop",               E3dCOORDINATE_TYPE,  ROffset(AngleTop) },

	{ "RefractiveIndexBottom",  E3dCOORDINATE_TYPE,  ROffset(RefractiveIndexBottom) },
	{ "AngleBottom",            E3dCOORDINATE_TYPE,  ROffset(AngleBottom) },
};
#undef ROffset


typedef struct
{
	XeWidget  RefractiveIndexTopW, RefractiveIndexBottomW, AngleTopW, AngleBottomW;
} E3dResourcesGUI;


static E3dNodeComponentClass*	_NodeComponentClass = NULL;



static EChangeFlags _Update(E3dNode* PChangedBranchRootNode, E3dNode* PNode, E3dNodeComponent* PInfo, const EChangeFlags PChanged, const EBool PCheckOnly)
{
	if(PCheckOnly) return(0);

	E3dComponent*  LComponent = (E3dComponent*)PInfo;
	E3dScene*      LScene = E3d_Scene;
	E3dModel*      LModel = (E3dModel*)PNode;
	E3dSpline*     LSpline0 = NULL;
	E3dSpline*     LSpline1 = NULL;
	E3dSpline*     LSpline2 = NULL;
	E3dSpline*     LSpline3 = NULL;
	EChangeFlags   LTriggeredChanges = 0;

	if(LComponent == NULL)
	{
		LComponent = (E3dComponent*)E3dNodeComponent_Add((E3dNode*)LModel, _NodeComponentClass);
	}

	if(!LComponent->SplinesCreated)
	{
		LSpline0 = (E3dSpline*)E3dModel_ObjectAddNew(LModel, E3dSpline_class);
		EObject_SetName(LSpline0, E3dScene_GetUniqueNodeName(LScene, "top"), FALSE);

		LSpline1 = (E3dSpline*)E3dModel_ObjectAddNew(LModel, E3dSpline_class);
		EObject_SetName(LSpline1, E3dScene_GetUniqueNodeName(LScene, "bottom"), FALSE);

		LSpline2 = (E3dSpline*)E3dModel_ObjectAddNew(LModel, E3dSpline_class);
		EObject_SetName(LSpline2, E3dScene_GetUniqueNodeName(LScene, "bottomV"), FALSE);

		LSpline3 = (E3dSpline*)E3dModel_ObjectAddNew(LModel, E3dSpline_class);
		EObject_SetName(LSpline3, E3dScene_GetUniqueNodeName(LScene, "topV"), FALSE);
		LComponent->SplinesCreated = TRUE;
	}

	if(PChanged == 0)
	{
// Just check consistency
//
		return(0);
	}
	else
	{
		if(LModel->Objects.Count == 4)
		{
			LSpline0 = E_DynamicCast(LModel->Objects.Items[0], E3dSpline);
			LSpline1 = E_DynamicCast(LModel->Objects.Items[1], E3dSpline);
			LSpline2 = E_DynamicCast(LModel->Objects.Items[2], E3dSpline);
			LSpline3 = E_DynamicCast(LModel->Objects.Items[3], E3dSpline);
		}
	}

	if(PChanged & E3dChgATTRIBUTES && LSpline0 && LSpline1 && LSpline2 && LSpline3)
	{
		if(LSpline0->CVs.Count != 2) EResizeNCA(E3dSplineCV, 2,  LSpline0->CVs.Items, LSpline0->CVs.Count, LSpline0->CVs.CountAllocated);
		if(LSpline1->CVs.Count != 2) EResizeNCA(E3dSplineCV, 2,  LSpline1->CVs.Items, LSpline1->CVs.Count, LSpline1->CVs.CountAllocated);
		if(LSpline2->CVs.Count != 2) EResizeNCA(E3dSplineCV, 2,  LSpline2->CVs.Items, LSpline2->CVs.Count, LSpline2->CVs.CountAllocated);
		if(LSpline3->CVs.Count != 2) EResizeNCA(E3dSplineCV, 2,  LSpline3->CVs.Items, LSpline3->CVs.Count, LSpline3->CVs.CountAllocated);

		LTriggeredChanges |= E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY;

		const E3dCoordinate  LLen = 10.0;

		E3dSplineCV*	LCV;
		LCV = LSpline0->CVs.Items;
		E3dSplineCV_Init(LCV, E3dSplineLINEAR);LCV->Position = E3dCoord3_s(0.0, 0.0, 0.0);LCV++;

		const E3dCoordinate  LTopAngle = 90.0 - LComponent->AngleTop;
		E3dCoordinate  LX = E3d_Cos(LTopAngle * E3dDEGREES_TO_RADIANS), LY = sin(LTopAngle * E3dDEGREES_TO_RADIANS);
		{
			E3dSplineCV_Init(LCV, E3dSplineLINEAR);LCV->Position = E3dCoord3_s(LX * LLen, LY * LLen, 0.0);LCV++;
		}

		LCV = LSpline1->CVs.Items;
		E3dSplineCV_Init(LCV, E3dSplineLINEAR);LCV->Position = E3dCoord3_s(0.0, 0.0, 0.0);LCV++;

		const E3dCoordinate  LEta12 = LComponent->RefractiveIndexTop / LComponent->RefractiveIndexBottom;
		LComponent->AngleBottom = asin(LX * LEta12) * E3dRADIANS_TO_DEGREES;

		E3dRay  LNewRay;

		{
			E3dRay           LTopRay;LTopRay.Direction = E3dCoord3_s(-LX, -LY, 0.0);
			const E3dCoord3  LNormal = E3dCoord3_s(0.0, 1.0, 0.0);
			if(E3dRay_Refract(&LTopRay, &LNewRay, &LNormal, LEta12))
			{
				E3dSplineCV_Init(LCV, E3dSplineLINEAR);LCV->Position = E3dCoord3_s(LNewRay.Direction.X * LLen, LNewRay.Direction.Y * LLen, 0.0);LCV++;
			}
			else
			{
// Total internal reflection - indicate it with a 0 length vector
//
				LNewRay.Direction.X = LNewRay.Direction.Y = LNewRay.Direction.Z = 0.0;
			}
		}

		const E3dCoordinate  LVOffset = 1.0;

		LCV = LSpline2->CVs.Items;
		E3dSplineCV_Init(LCV, E3dSplineLINEAR);LCV->Position = E3dCoord3_s(LVOffset, 0.0, 0.0);LCV++;
		E3dSplineCV_Init(LCV, E3dSplineLINEAR);LCV->Position = E3dCoord3_s(LVOffset + LNewRay.Direction.X * LLen, LNewRay.Direction.Y * LLen, 0.0);LCV++;

		E3dRay  LNewRayV;

		LCV = LSpline3->CVs.Items;
		E3dSplineCV_Init(LCV, E3dSplineLINEAR);LCV->Position = E3dCoord3_s(LVOffset, 0.0, 0.0);LCV++;

		LNewRay.Direction.X *= -1.0;LNewRay.Direction.Y *= -1.0;
		const E3dCoord3  LNormal = E3dCoord3_s(0.0, -1.0, 0.0);
		if(E3dRay_Refract(&LNewRay, &LNewRayV, &LNormal, 1.0 / LEta12))
		{
			E3dSplineCV_Init(LCV, E3dSplineLINEAR);LCV->Position = E3dCoord3_s(LVOffset + LNewRayV.Direction.X * LLen, LNewRayV.Direction.Y * LLen, 0.0);LCV++;
		}
		else
		{
			E3dSplineCV_Init(LCV, E3dSplineLINEAR);LCV->Position = E3dCoord3_s(LVOffset, 0.0, 0.0);LCV++;
		}

		E3dNode_UpdateForDisplay(PNode, E3dUpdateSplineSHAPE);
	}

	return(LTriggeredChanges);
}


//================================================
// ToolBar icon callback handler
//================================================
static void _CB(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*  LScene = E3d_Scene;

	E3dModel*  LModel = E3dModel_CustomNew(E3d_Scene, _NodeComponentClass, NULL, FALSE);if(LModel)
	{
		E3dScene_NodeHrcAdd(E3d_Scene, (E3dNode*)LModel, E3dCallSCENE_CALLBACKS);
		E3dScene_ObjectsUpdateModelList(LScene);
		E3dScene_LayoutOnSchematics(LScene, TRUE);
	}

	if(((XePushButtonCallbackStruct*)PCallData)->Reason == XeCR_ACTIVATE_OPTIONS) E3dNode_Edit((E3dNode*)LModel, _NodeComponentClass);

	E3dWindows_RedrawByChangeFlags(LScene, E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY|E3dChgHIERARCHY);
}


//========================================
// Constructor
//========================================
static int _Init(E3dNode* PNode, E3dNodeComponent* PInfo)
{
	E3dComponent*  LThis = (E3dComponent*)PInfo;

	LThis->RefractiveIndexTop = 1.0; LThis->RefractiveIndexBottom = 1.0;
	LThis->AngleTop = 20.0, LThis->AngleBottom = 20.0;;
	LThis->SplinesCreated = FALSE;

	return(E_SUCCESS);
}


#define _M_Copy(mField)  LDComponent->mField = LSComponent->mField

//========================================
// Copy-constructor
//========================================
static int _Copy(const E3dNode* PSrcNode, const E3dNodeComponent* PSrcComponent,  E3dNode* PDstNode, E3dNodeComponent* PDstComponent)
{
	const E3dComponent*  LSComponent = (E3dComponent*)PSrcComponent;
	E3dComponent*        LDComponent = (E3dComponent*)PDstComponent;

	_M_Copy(RefractiveIndexTop);
	_M_Copy(RefractiveIndexBottom);
	_M_Copy(AngleTop);
	_M_Copy(AngleBottom);

	return(E_SUCCESS);
}


//========================================
// Dialog box callback handler
//========================================
static void _DCB_RefractiveIndexTop(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;
	E3dComponent*  LComponent = (E3dComponent*)E3dNodeComponent_ByClass(LNode, _NodeComponentClass, FALSE);

	LComponent->RefractiveIndexTop = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;

	_Update(NULL, LNode, (E3dNodeComponent*)LComponent, E3dChgATTRIBUTES, FALSE);

	E3dScene*  LScene = E3d_Scene;
	E3dWindows_RedrawByChangeFlags(LScene, E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY);
}


//========================================
// Dialog box callback handler
//========================================
static void _DCB_AngleTop(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;
	E3dComponent*  LComponent = (E3dComponent*)E3dNodeComponent_ByClass(LNode, _NodeComponentClass, FALSE);

	LComponent->AngleTop = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;

	_Update(NULL, LNode, (E3dNodeComponent*)LComponent, E3dChgATTRIBUTES, FALSE);

	E3dNodeComponentClass*  LClass = NULL;Xe_GetValuesVa(PW,  XeNUserData0, &LClass,  NULL);

	{
		XeComponentClassDrawer*  LDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, LClass);if(LDrawer)
		{
			E3dResourcesGUI*  LResourcesGUI = (E3dResourcesGUI*)(LDrawer->ResourcesGUI);

			XeScale_SetValue_Coord(LResourcesGUI->AngleBottomW, LComponent->AngleBottom, FALSE);
		}
	}

	E3dScene*  LScene = E3d_Scene;
	E3dWindows_RedrawByChangeFlags(LScene, E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY);
}


//========================================
// Dialog box callback handler
//========================================
static void _DCB_RefractiveIndexBottom(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;
	E3dComponent*  LComponent = (E3dComponent*)E3dNodeComponent_ByClass(LNode, _NodeComponentClass, FALSE);

	LComponent->RefractiveIndexBottom = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;

	_Update(NULL, LNode, (E3dNodeComponent*)LComponent, E3dChgATTRIBUTES, FALSE);

	E3dScene*  LScene = E3d_Scene;
	E3dWindows_RedrawByChangeFlags(LScene, E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY);
}


//========================================
// Dialog box callback handler
//========================================
static void _DCB_AngleBottom(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;
	E3dComponent*  LComponent = (E3dComponent*)E3dNodeComponent_ByClass(LNode, _NodeComponentClass, FALSE);

	LComponent->AngleBottom = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;

	_Update(NULL, LNode, (E3dNodeComponent*)LComponent, E3dChgATTRIBUTES, FALSE);

	E3dScene*  LScene = E3d_Scene;
	E3dWindows_RedrawByChangeFlags(LScene, E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY);
}


static void _GUI(E3dNode* PNode, E3dNodeComponent* PInfo, XeInfoPanel* PPanel, void* PDrawer, const int PAction)
{
	XeComponentClassDrawer*  LDrawer = (XeComponentClassDrawer*)PDrawer;
	E3dComponent*            LComponent = (E3dComponent*)PInfo;

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

					XeScale_SetValue_Coord(LResourcesGUI->AngleTopW,              LComponent->AngleTop,              FALSE);
					XeScale_SetValue_Coord(LResourcesGUI->RefractiveIndexTopW,    LComponent->RefractiveIndexTop,    FALSE);
					XeScale_SetValue_Coord(LResourcesGUI->AngleBottomW,           LComponent->AngleBottom,           FALSE);
					XeScale_SetValue_Coord(LResourcesGUI->RefractiveIndexBottomW, LComponent->RefractiveIndexBottom, FALSE);
				}
				else
				{
					XeWidget          LTopMatrixW = LDrawer->Drawer.DrawerW, LW;
					XeWidget          LIndentWs[7];int	LIndentN;      // For indenting the Scale Widgets
					XeWidget          LTraverseWs[16];int	LTraverseN;  // For TextField traversal
					EArg              Args[256];int  ArgCnt;
					E3dResourcesGUI*  LResourcesGUI;ENew(LResourcesGUI);

					LDrawer->ResourcesGUI = LResourcesGUI;

					EXeStart;
					EXeSetArg(XeNLabelAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNFontList, Xe_DropDownPanelFontList);
					EXeSetArg(XeNMarginTop, 5);
					EXeSetArg(XeNResizableY, FALSE);
					XeWidget_ManagedNew("Top side", XeLabelWidget_class, LTopMatrixW, Args, ArgCnt);

					LTraverseN = 0;
					LIndentN = 0;
					EXeStart;
					EXeSetArg(XeNScaleLengthInPixels, 120);
					EXeSetArg(XeNTextFieldDigits, 6);EXeSetArg(XeNDecimalPoints, 4);
					EXeSetArg(XeNIncrement, 10);
					EXeSetArg(XeNResizableY, FALSE);
					EXeSetArg(XeNUserData0, LComponent->Class);
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->RefractiveIndexTopW = LW = XeScale_New_Coord(LTopMatrixW, "Refractive index", Args, ArgCnt);
					XeScale_SetValueAndRange_Coord(LW,  LComponent->RefractiveIndexTop,  0.001, 10.0,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_RefractiveIndexTop, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNScaleLengthInPixels, 120);
					EXeSetArg(XeNTextFieldDigits, 6);EXeSetArg(XeNDecimalPoints, 4);
					EXeSetArg(XeNIncrement, 10);
					EXeSetArg(XeNResizableY, FALSE);
					EXeSetArg(XeNUserData0, LComponent->Class);
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->AngleTopW = LW = XeScale_New_Coord(LTopMatrixW, "Angle", Args, ArgCnt);
					XeScale_SetValueAndRange_Coord(LW,  LComponent->AngleTop,  0.0, 90.0,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_AngleTop, (EPointer)PPanel);


					EXeStart;
					EXeSetArg(XeNLabelAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNFontList, Xe_DropDownPanelFontList);
					EXeSetArg(XeNMarginTop, 5);
					EXeSetArg(XeNResizableY, FALSE);
					XeWidget_ManagedNew("Bottom side", XeLabelWidget_class, LTopMatrixW, Args, ArgCnt);


					EXeStart;
					EXeSetArg(XeNScaleLengthInPixels, 120);
					EXeSetArg(XeNTextFieldDigits, 6);EXeSetArg(XeNDecimalPoints, 4);
					EXeSetArg(XeNIncrement, 10);
					EXeSetArg(XeNResizableY, FALSE);
					EXeSetArg(XeNUserData0, LComponent->Class);
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->RefractiveIndexBottomW = LW = XeScale_New_Coord(LTopMatrixW, "Refractive index", Args, ArgCnt);
					XeScale_SetValueAndRange_Coord(LW,  LComponent->RefractiveIndexBottom,  0.001, 10.0,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_RefractiveIndexBottom, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNScaleLengthInPixels, 120);
					EXeSetArg(XeNTextFieldDigits, 6);EXeSetArg(XeNDecimalPoints, 4);
					EXeSetArg(XeNIncrement, 10);
					EXeSetArg(XeNResizableY, FALSE);
					EXeSetArg(XeNUserData0, LComponent->Class);
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->AngleBottomW = LW = XeScale_New_Coord(LTopMatrixW, "Angle", Args, ArgCnt);
					XeScale_SetValueAndRange_Coord(LW,  LComponent->AngleBottom,  0.0, 90.0,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_AngleBottom, (EPointer)PPanel);

					XeWidgets_IndentFromLeft(LIndentWs, LIndentN, 4);

// Set up TextField traversal
//
					XeTraversal_SetRing(LTraverseWs, LTraverseN);
				}
			}
		break;

		case E_FREE:
		break;
	}
}


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

	{
		E3dNodeComponentClass  LClass =
		{
			.Name=        "Refraction",
			.StructSize=  sizeof(E3dComponent),
			.Resources=   EListS_s(_Resources),
			.Init=        _Init,
			.Copy=        _Copy,
			.Update=      _Update,
			.GUI=         _GUI
		};
		_NodeComponentClass = E3dNodeComponent_ClassRegister(&LClass);
	}

	_Plugin = PPlugin;

	if(_NodeComponentClass)
	{
// Use default plugin icon
//
		XePushButton_AddImg3(_ToolBarButtons[_ToolBarButtons_Count++], Plugin, "Tool->Create", "Refraction", '\0', NULL, NULL, FALSE, "This tool demonstrates Snell's law of refraction", _CB, (EPointer)0);
	}

	return(E_SUCCESS);
}


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

	if(_NodeComponentClass) { E3dNodeComponent_ClassDeactivate(_NodeComponentClass);_NodeComponentClass = NULL; }

	return(E_SUCCESS);
}
