//================================================================================================================================
// Plugin for EQUINOX-3D
//
// This sample code shows how to:
//  - Create a simple dialog box with a button. When the button is pressed:
//  - Look up runtime Object classes by name, in this case, the surface tooks "Revolve" and "Extrude".
//  - Create Objects of those classes
//  - Build a simple Model hierarchy, using those primitive shapes
//  - Set transform values
//  - Add the hierarchy to the Scene, and redraw the 3DWindows
//
// See Examples/BuildHierarchy.c for more details about hierarchies and transforms.
// To avoid cluttering the UI, example plugins are not loaded by default, when EQUINOX-3D is started.
// You can manually load them from the Plugin manager, or you can edit your Plugins/Plugins.list file,
// and comment out (e.g. with a trailing "#") or remove the "Load = FALSE;" line for a plugin.
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Jan-07 02:45:44
//
// 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 <Xe/PushButton.h>
#include <Xe/Shell.h>
#include <Xe/Dialog.h>


char*                E_PluginInfo = "Sample code: create surfaces with the Revolve and Extrude, runtime-defined classes / tools.";

static EPlugin*      _Plugin = NULL;

static XeWidget      _DialogW = NULL;
static EPluginLock*  _DialogLock = NULL;

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



enum
{
	CR_TEST = XeCR_CUSTOM0,
};


//========================================
// Dialog box callback
//========================================
static void _DCB(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*  LScene = E3d_Scene;

// Create customized primitives (Box, Cylinder, Torus).
//
	{
// Look up the classes. Bail, if one or more of the classes don't exist (e.g. the appropriate plugin is not loaded)
// We only need to do this once, so these could even be global variables in your plugin.
//
		E3dObjectComponentClass*  LRevolveClass = E3dObjectComponent_ClassByName("Revolve");if(LRevolveClass == NULL) return;
		E3dObjectComponentClass*  LExtrudeClass = E3dObjectComponent_ClassByName("Extrude");if(LExtrudeClass == NULL) return;

		{
// Create ObjectComponent templates. These are used for setting user-adjustable parameters before creating an Object.
// These can be reused.
//
			E3dObjectComponent*  LRevolveInfo = E3dObjectComponent_New(LRevolveClass);
			E3dObjectComponent*  LExtrudeInfo = E3dObjectComponent_New(LExtrudeClass);

			{
// Set Resources by name.
// We use the Resource system, as we don't know the memory layout of runtime-typed objects, outside the plugin that implements them.
//
// Alternatively, if you set a Resource frequently (thousands of times), you could do this:
//
// Look up Resource by name once and get handle:
//  EResource*     LRadiusResource = EObject_ResourceByName(LInfo, "Radius");
//
// Set value using the handle:
//  EResource_Set1f(LInfo, LRadiusResource, 2.0);
// This avoids the name-look-ups, so it's faster.
//


// Create an empty Model for the root of our hierarchy. This is how you create any object in the "E_" object-oriented system, from EpImage, to E3dModel.
				E3dModel*  LRootModel;E_NewNamed(E3dModel, "Surfaces",  LRootModel);


// Create a cylinder, using the Revolve tool
				{
// Create a Model, and add it to the root Model.
					E3dModel*  LModel;E_New(E3dModel, LModel);EObject_SetName(LModel, EStrDup("Cylinder"), FALSE);
					LModel->TransformSet.Translation.X =  -1.0;
//				LModel->TransformSet.EulerRotation.Angles.Z = 90.0;

					E3dNode_AddChild((E3dNode*)LRootModel, (E3dNode*)LModel);

// First, we need to create a profile Spline for the Revolve
					const E3dCoordinate  LTopRadius = 0.5, LBottomRadius = 1.0, LHeight = 2.0;
					E3dSpline*  LSpline;E_NewNamed(E3dSpline, "Profile",  LSpline);if(LSpline)
					{
						LSpline->SplineType = E3dSplineBEZIER;  // Make the Spline Bezier, for flexibility. But, we'll just create linear segments here.

// Create Spline CVs in counter-clockwise order, so the revolved surface will be the correct side out.
						const EIndex  LNCVs = 4;
						E3dSplineCV*  LCV = E3dSplineCVs_New(E3dSplineLINEAR, LNCVs);
						E3dSpline_SetCVs(LSpline, LCV, LNCVs);
						LCV->Position = E3dCoord3_s(0.0, 0.0, 0.0);LCV++;
						LCV->Position = E3dCoord3_s(LBottomRadius, 0.0,     0.0);LCV++;
						LCV->Position = E3dCoord3_s(LTopRadius, LHeight, 0.0);LCV++;
						LCV->Position = E3dCoord3_s(0.0, LHeight, 0.0);LCV++;
						E3dSpline_CreateLinearSegments(LSpline);
					}

// For simplicity, we'll instantiate both the profile Spline and the revolved surface on this Model
					E3dModel_ObjectAppend(LModel, (E3dObject*)LSpline, FALSE);

					EObject_NamedResourceSet1i(LRevolveInfo, "Sides",          36);            // Steps for the revolve
					EObject_NamedResourceSet1f(LRevolveInfo, "SmoothingAngle", 30.0);          // Normal smoothing angle for the Revolve
					EObject_NamedResourceSetObject(LRevolveInfo, "ProfileGeometry", LSpline);  // This is how you pass an Object as a Resource. In this case, the profile Spline to be revolved

					E3dObject*  LCylinderMesh = E3dObject_CustomNewInit(LScene, LRevolveInfo, TRUE);if(LCylinderMesh)
					{
						E3dModel_ObjectAppend(LModel, LCylinderMesh, FALSE);
					}
				}


// Create an beveled block, using the Extrude tool
				{
// Create a Model, and add it to the root Model.
					E3dModel*  LModel;E_New(E3dModel, LModel);EObject_SetName(LModel, EStrDup("Extrude"), FALSE);
					LModel->TransformSet.Translation.X =  1.0;
					LModel->TransformSet.Translation.Y =  1.0;
//				LModel->TransformSet.EulerRotation.Angles.Z = 90.0;

					E3dNode_AddChild((E3dNode*)LRootModel, (E3dNode*)LModel);

// First, we need to create a profile Spline for the Extrude.
// We'll make a nice, bevelled box, by creating a Spline with rounded corners, made of Bezier segments.
// Then, we'll make the Extrude tool bevel the fornt and the back of the box.
					const E3dCoordinate  LSizeX = 2.0, LSizeY = 1.0, LCornerRadius = 0.6;
					E3dSpline*  LSpline;E_NewNamed(E3dSpline, "Profile",  LSpline);if(LSpline)
					{
						LSpline->NumOfLinSteps = 4;             // We'll use 4 linear segments for each Bezier segment of the Spline, uniformly. We could specify this per segment, if we wanted to
						LSpline->SplineType = E3dSplineBEZIER;  // Make the Spline Bezier, for flexibility. But, we'll just create linear segments here.
						LSpline->Closed     = TRUE;             // Close this Spline (create a segment between the last and the first CV)

// Create Spline CVs in counter-clockwise order, so the revolved surface will be the correct side out.
						const EIndex  LNCVs = LCornerRadius > 0.0 ? 8 : 4;
						const E3dCoordinate  LRH = LCornerRadius / 2.0;
						E3dSplineCV*  LCV = E3dSplineCVs_New(E3dSplineLINEAR, LNCVs);
						E3dSpline_SetCVs(LSpline, LCV, LNCVs);
						if(LCornerRadius > 0.0)
						{
// Bottom edge
							LCV->Position = E3dCoord3_s(-LSizeX / 2.0 + LRH,  -LSizeY / 2.0,        0.0);LCV->Previous = LCV->Position;LCV++;
							LCV->Position = E3dCoord3_s(LSizeX / 2.0 - LRH,  -LSizeY / 2.0,        0.0);LCV->Next = LCV->Position;LCV->Segment.Shape = E3dSplineBEZIER;LCV++;

// Right edge
							LCV->Position = E3dCoord3_s(LSizeX / 2.0,        -LSizeY / 2.0 + LRH,  0.0);LCV->Previous = LCV->Position;LCV->Previous.Y = -LSizeY / 2.0;LCV++;
							LCV->Position = E3dCoord3_s(LSizeX / 2.0,         LSizeY / 2.0 - LRH,  0.0);LCV->Next = LCV->Position;LCV->Next.Y = LSizeY / 2.0;LCV->Segment.Shape = E3dSplineBEZIER;LCV++;

// Top edge
							LCV->Position = E3dCoord3_s(LSizeX / 2.0 - LRH,   LSizeY / 2.0,        0.0);LCV->Previous = LCV->Position;LCV++;
							LCV->Position = E3dCoord3_s(-LSizeX / 2.0 + LRH,   LSizeY / 2.0,        0.0);LCV->Next = LCV->Position;LCV->Segment.Shape = E3dSplineBEZIER;LCV++;

// Left edge
							LCV->Position = E3dCoord3_s(-LSizeX / 2.0,         LSizeY / 2.0 - LRH,  0.0);LCV->Previous = LCV->Position;LCV->Previous.Y = LSizeY / 2.0;LCV++;
							LCV->Position = E3dCoord3_s(-LSizeX / 2.0,        -LSizeY / 2.0 + LRH,  0.0);LCV->Next = LCV->Position;LCV->Next.Y = -LSizeY / 2.0;LCV->Segment.Shape = E3dSplineBEZIER;LCV++;
						}
						else
						{
							LCV->Position = E3dCoord3_s(-LSizeX / 2.0, -LSizeY / 2.0, 0.0);LCV++;
							LCV->Position = E3dCoord3_s(LSizeX / 2.0, -LSizeY / 2.0, 0.0);LCV++;
							LCV->Position = E3dCoord3_s(LSizeX / 2.0,  LSizeY / 2.0, 0.0);LCV++;
							LCV->Position = E3dCoord3_s(-LSizeX / 2.0,  LSizeY / 2.0, 0.0);LCV++;
						}
						E3dSpline_CreateLinearSegments(LSpline);
					}

// For simplicity, we'll instantiate both the profile Spline and the revolved surface on this Model
					E3dModel_ObjectAppend(LModel, (E3dObject*)LSpline, FALSE);

					EObject_NamedResourceSet1f(LExtrudeInfo, "Depth",         2.0);            // Depth (length) of the extrude
					EObject_NamedResourceSet1i(LExtrudeInfo, "Sides",          36);            // Steps for the extrude
					EObject_NamedResourceSet1i(LExtrudeInfo, "StartBevel.Steps",       4);
					EObject_NamedResourceSet1f(LExtrudeInfo, "StartBevel.Radius",    0.2);
					EObject_NamedResourceSet1i(LExtrudeInfo, "EndBevel.Steps",         4);
					EObject_NamedResourceSet1f(LExtrudeInfo, "EndBevel.Radius",      0.1);
					EObject_NamedResourceSet1f(LExtrudeInfo, "SmoothingAngle", 30.0);          // Normal smoothing angle for the Revolve
					EObject_NamedResourceSetObject(LExtrudeInfo, "ProfileGeometries", LSpline);  // This is how you pass an Object as a Resource. In this case, the profile Spline to be revolved

					E3dObject*  LExtrudeMesh = E3dObject_CustomNewInit(LScene, LExtrudeInfo, TRUE);if(LExtrudeMesh)
					{
printf("%s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
						E3dModel_ObjectAppend(LModel, LExtrudeMesh, FALSE);
					}
				}


// Free our Component templates
				E3dObjectComponent_Free(LRevolveInfo);
				E3dObjectComponent_Free(LExtrudeInfo);

				E3dNodeHrc_UpdateConnections((E3dNode*)LRootModel);     // Update all internal connections in our hierarchy, at once
				E3dNodeHrc_UpdateMatrices((E3dNode*)LRootModel, TRUE);  // Update the Matrices of our new hierarchy, to make the hierarcy ready for rendering

// Add our hierarcy to the main Scene
				E3dScene_NodeHrcAdd(LScene, (E3dNode*)LRootModel, E3dCallSCENE_CALLBACKS);

// These 2 calls are needed by EQUINOX-3D, so our new hierarchy will work correctly
				E3dScene_ObjectsUpdateModelList(LScene);
				E3dScene_LayoutOnSchematics(LScene, TRUE);


// This is needed, so the intelligent update system in libE3D / EQUINOX-3D knows what changed. We will be OR-ing flags to this, to indicate what we are changing in the Scene.
// In this case, we will be changing Meshes, but if we were just changing Splines, we wouldn't want all the ray-traced views to start redrawing.
				EChangeFlags  LChanged = E3dNodeHrc_GetChangeFlagsByObjects((E3dNode*)LRootModel) | E3dChgHIERARCHY;

				E3dWindows_RedrawByChangeFlags(LScene, LChanged);
			}
		}
	}
}


//========================================
// Dialog box callback
//========================================
static void _DCB_Close(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	XeCallbackStruct*  LCBS = (XeCallbackStruct*)PCallData;
	if(LCBS->Reason == XeCR_PROTOCOLS)  // Check this because this callback may receive XeCR_FULLSCREEN_OFF/ON calls, too
	{
// Un-display dialog box
//
		XeWidget_Unmap(_DialogW);

// Delete plugin lock
//
		EPluginLock_Del(_Plugin, &_DialogLock, FALSE);
	}
}


static int _DialogLockDel(EPlugin* PPlugin, EPluginLock* LLock)
{
	XeWidget_Unmap(_DialogW);

	return(E_SUCCESS);
}


//================================================
// ToolBar icon callback handler
//================================================
static void _CB(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
// We'll be displaying a dialog box, so we'll lock the plugin to prevent unloading
// it (and pulling the rag out from beneath our global variables).
// When the application exits, or the user forces the plugin to unload, _DialogLockDel will get called before E_PluginExit.
// This two-pass exit (unlock & unload) of plugins is necessary to avoid certain inter-dependencies from breaking.
//
	if(_DialogLock == NULL) _DialogLock = EPluginLock_Add(_Plugin, "BuildSurfacesDialog", _DialogLockDel);

	if(_DialogW == NULL)
	{
		XeWidget LW;
		EArg     Args[32];int  ArgCnt;
		EXeStart;
		EXeSetArg(XeNAllowShellResize, TRUE);
		EXeSetArg(XeNButtonsStretch, FALSE);
		EXeSetArg(XeNWorkAreaType, XeMATRIX);
		EXeSetArg(XeNWMDecorations, XeWMDecorCLOSE|XeWMDecorBORDER|XeWMDecorRESIZEH|XeWMDecorTITLE);
		EXeSetArg(XeNTitle, "Surfaces");EXeSetArg(XeNIconName, "Surfaces");
		XeWidget  LDialogW = XeDialog_New(XeDIALOG_TEMPLATE, "Surfaces", E3dp_TopShellW,  Args, ArgCnt);if(LDialogW == NULL) return;
		XeAddCallback(LDialogW, XeNWMCallback, _DCB_Close, (EPointer)NULL);

		_DialogW = LDialogW;

		XeWidget  LTopMatrixW = XeDialog_GetChild(LDialogW, XeDIALOG_WORK_AREA);

		EXeStart;
		EXeSetArg(XeNHighlightThickness, 0);
		EXeSetArg(XeNMarginSizeX, 8);EXeSetArg(XeNMarginSizeY, 8);
		EXeSetArg(XeNResizableX, TRUE);
		Xe_SetValues(LTopMatrixW, Args, ArgCnt);

		EXeStart;
		EXeSetArg(XeNHighlightThickness, 0);
		EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignCENTER);
		EXeSetArg(XeNResizableY, FALSE);
		LW = XeWidget_ManagedNew("Create surfaces", XePushButton_class, LTopMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNActivateCallback, _DCB, (EPointer)NULL);


		XeShell_Raise(_DialogW);
		XeShell_CenterToWidget(_DialogW, E3dp_TopShellW);
	}
	else XeShell_Raise(_DialogW);
}


static const char*  _ToolTip = "This plugin demonstrates how to create surfaces from code, using the Revolve and Extrude tools.";

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

// Use default plugin icon
//
	XePushButton_AddImg3(_ToolBarButtons[_ToolBarButtons_Count++], Plugin, "Tool->Create", "Sample:BuildSurfaces", '\0', NULL, NULL, FALSE, _ToolTip, _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;
	}

	return(E_SUCCESS);
}
