//================================================================
// EQUINOX-3D plugin
//
// Voxel renderer (MRI, CAT etc.)
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Aug-04 21:59:26
//
// Copyright (C) 2014-2026 by Gabor Nagy. All rights reserved.
//================================================================
#include <math.h>
#include <stdio.h>

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

#include <Xe/Matrix.h>
#include <Xe/Scale.h>
#include <Xe/Separator.h>
#include <Xe/ToggleButton.h>


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

#include <E3D/GL/Draw.h>
#include <E3D/Rendering.h>
#include <E3D/SL.h>

// Cg
//
//#include <Cg/cgGL.h>
//#include "../Rendering/GLSL/GLSL.h"


static EPlugin*  _Plugin = NULL;

enum
{
	E3dPO_XY = 0,
	E3dPO_XZ
};


extern char      Examples_Voxels_frg_glsl[];

E3dShaderClass*  _Shader_class = NULL;


typedef struct
{
	E3dShaderCore;

	EFloat32_3  BBoxMin, BBoxMax, BBoxSize;
	GLuint      GLVoxels;  // GL ID of 3D texture
} E3dThisShader;


// These will be passed to the fragment program core
//
#define ROffset(field) (void*)(EPtrOffset(E3dThisShader, field))
static EResource  _SL_Resources_a[]=
{
	{ NULL,             EResFLOAT32_3,  .Semantic= (char*)E3dSLSemantic_Position },
	{ NULL,             EResFLOAT32_3,  .Semantic= (char*)E3dSLSemantic_PointToEye },
	{ "BBoxMin",        EResFLOAT32_3,  ROffset(BBoxMin) },
	{ "BBoxMax",        EResFLOAT32_3,  ROffset(BBoxMax) },
	{ "BBoxSize",       EResFLOAT32_3,  ROffset(BBoxSize) },
	{ "Voxels",         EResCUSTOM,     ROffset(GLVoxels) },
};
#undef ROffset
static EResourceList  _SL_Resources = EListS_s(_SL_Resources_a);



static char*  _FileNameExtensions[]=
{
	".uvd", ".UVD", NULL
};

static void*  _IOModule = NULL;


static XeDWidget  _MenuButton = NULL, _ToolBarButton = NULL;

EpIconDef3I(static, Grid);


//================================================================
// UpdateProc of the shader
// This function is called when one or more Shader Resources
// change, which could happen through the UI, animation etc.
// It also happens when the user changes shader type (Class)
// and the runtime attempts to convert from the old Class to the
// new one. This is why we need sanity checks on the Resources.
//================================================================
static int _ShaderUpdate(E3dMaterial* PMaterial, E3dShader* PShader, const E3dRenderer* PRenderer, E3dModel* PModel, E3dObject* PObject, EObject* PShadeable, const EChangeFlags PChanged)
{
	E3dGeometry*  LGeometry = E_DynamicCast(PObject, E3dGeometry);if(LGeometry)
	{
		E3dBBox  LBBox;if(E3dGeometry_GetBoundingBox(LGeometry, E3dBB_ALL,  &LBBox))
		{
			E3dThisShader*  LShader = (E3dThisShader*)PShader;

// LShader->BBox* has is always float32_3, but LBB* is E3dCoord3
//
			LShader->BBoxMin.X = LBBox.Min.X;
			LShader->BBoxMin.Y = LBBox.Min.Y;
			LShader->BBoxMin.Z = LBBox.Min.Z;

			LShader->BBoxMax.X = LBBox.Max.X;
			LShader->BBoxMax.Y = LBBox.Max.Y;
			LShader->BBoxMax.Z = LBBox.Max.Z;

			E_Sub3_vvv(LBBox.Max, LBBox.Min, LShader->BBoxSize);
		}
	}

	return(E_SUCCESS);
}


//========================================
// Shader constructor
//========================================
static void _ShaderInit(E3dShader* PShader)
{
	E3dThisShader*  LShader = (E3dThisShader*)PShader;

	LShader->BBoxMin = EFloat32_3_s(0.0, 0.0, 0.0);
	LShader->BBoxMax = EFloat32_3_s(0.0, 0.0, 0.0);
}



//========================================================
// Check a file if it is an STL model
//========================================================
static int _FormatCheck(const char* PFileName, FILE* PFile,  const uint8_t* PBuffer, const EIndex PBufferSize)
{
	if(PFileName)
	{
		const int  LLen = strlen(PFileName);if(LLen > 4)
		{
			if(EStr_Equal(PFileName + LLen - 4, ".uvd")) return(E_SUCCESS);
			else if(EStr_Equal(PFileName + LLen - 4, ".UVD")) return(E_SUCCESS);
		}
		return(E_UNRECOGNIZED_FORMAT);
	}
	else return(E_NO_FILENAME);
}


//========================================
// Read an STL Model
//========================================
static int _ModelLoad(E3dFile* P3DFile, E3dModel** PModelRet)
{
	if(PModelRet) *PModelRet = NULL;

	if(P3DFile->File == NULL) return(E_UNSUPPORTED_SOURCE);

	uint8_t*  LBuffer = NULL;
	size_t    LFileLength;

	FILE*  LFile = P3DFile->File;if(LFile)
	{
		fseek(LFile, 0, SEEK_END);
		LFileLength = ftell(LFile);
		fseek(LFile, 0, SEEK_SET);

		LBuffer = EMalloc(LFileLength);if(LBuffer)
		{
			fseek(LFile, 0, SEEK_SET);
			LFileLength = fread(LBuffer, 1, LFileLength, LFile);
		}
	}
	else if(P3DFile->MemoryBuffer)
	{
		LBuffer = P3DFile->MemoryBuffer;
		LFileLength = P3DFile->Size;
	}


	if(LBuffer)
	{
		const uint8_t*  LPtr = LBuffer;
		float           LExposure, LTubeCurrent, LSliceSpacing;

		E3dCoord3       LSampleSpacing;

		LPtr += 8;  // DCVXGN01
		EInt32_3   LResolution;
		EM_GetInt32I_be(LResolution.X, LPtr);
		EM_GetInt32I_be(LResolution.Y, LPtr);
		EM_GetInt32I_be(LResolution.Z, LPtr);


		printf("%s() %s:%d  %dx%dx%d\n", __FUNCTION__, __FILE__, __LINE__, LResolution.X, LResolution.Y, LResolution.Z);fflush(stdout);

		{ int  i;EM_GetInt32I_be(i, LPtr);LExposure    = (float)i * 0.0001; }
		{ int  i;EM_GetInt32I_be(i, LPtr);LTubeCurrent = (float)i * 0.0001; }
		{ int  i;EM_GetInt32I_be(i, LPtr);LSampleSpacing.X = (float)i * 0.0001; }
		{ int  i;EM_GetInt32I_be(i, LPtr);LSampleSpacing.Y = (float)i * 0.0001; }
		{ int  i;EM_GetInt32I_be(i, LPtr);LSampleSpacing.Z = (float)i * 0.0001; }
		{ int  i;EM_GetInt32I_be(i, LPtr);LSliceSpacing = (float)i * 0.0001; }

		const char  LComponentType  = *LPtr++;
		const char  LBytesPerSample = *LPtr++;

		char*  LStudyDesription = EStrDup((char*)LPtr);LPtr += strlen((char*)LPtr) + 1;

// Convert to meters
//
		printf("%s() %s:%d  %dx%dx%d\n", __FUNCTION__, __FILE__, __LINE__, LResolution.X, LResolution.Y, LResolution.Z);fflush(stdout);
		printf(
" Exposure             % 7.2fmAs\n Tube current         % 7.2fmA\n Slice size             % 5.2fmm x %.2fmm x %.2fmm\n\
 Slice spacing        %7.2fmm\n Sample component type   %d\n Bytes per sample        %d\n Study %s%s%s\n",
		 LExposure, LTubeCurrent, XYZ_s(LSampleSpacing), LSliceSpacing, LComponentType, LBytesPerSample, ECodeCyan, LStudyDesription, ECodeNormal);
		fflush(stdout);

		LSampleSpacing.X *= 0.001;
		LSampleSpacing.Y *= 0.001;
		LSampleSpacing.Z *= 0.001;
		LSliceSpacing    *= 0.001;

		EpPixelFormat  LPixelFormat;EpPixelFormat_Set(&LPixelFormat,  0, 0, 0);
		switch(LComponentType)
		{
			case 7:
				if(LBytesPerSample == 2) EpPixelFormat_Set(&LPixelFormat,  EpGRAY, 1, EResUINT16);
			break;
		}

		if(LPixelFormat.Type == 0)
		{
			*PModelRet = NULL;return(E_UNRECOGNIZED_FORMAT);
		}

		const EIndex  LNumOfPixelsPerSlice = LResolution.X * LResolution.Y;


		if(0)
		{
// True voxel rendering
//
			E3dObjectComponentClass*  LClass = E3dObjectComponent_ClassByName("Box");if(LClass)
			{
				E3dObjectComponent*  LInfo = E3dObjectComponent_New(LClass);if(LInfo)
				{
					EChangeFlags  LChanged = 0;(void)LChanged;

					EObject_NamedResourceSet1f(LInfo, "XEdgeLength", LSampleSpacing.X * (E3dCoordinate)LResolution.X);
					EObject_NamedResourceSet1f(LInfo, "YEdgeLength", LSampleSpacing.Y * (E3dCoordinate)LResolution.Y);
					EObject_NamedResourceSet1f(LInfo, "ZEdgeLength", LSampleSpacing.Z * (E3dCoordinate)LResolution.Z);

					E3dModel*  LModel = E3dModel_CustomObjectNewInit(E3d_Scene, LInfo, NULL, NULL, (E3dp_Prefs.Duplicate_Flags & E3dCopyMODEL_UNIQUE_NAME) != 0);
					E3dNodeHrc_UpdateMatrices((E3dNode*)LModel, TRUE);
					E3dScene_NodeHrcAdd(E3d_Scene, (E3dNode*)LModel, E3dCallSCENE_CALLBACKS);
					LChanged |= E3dModel_GetChangeFlagsByObjects(LModel) | E3dChgHIERARCHY;

					E3dObjectComponent_Free(LInfo);

					E_Assign(LModel, *PModelRet);  // Assign LModel to the return value, so it's properly reference counted / tracked

					if(LModel && LModel->Objects.Count > 0)
					{
						E3dMesh*       LMesh = (E3dMesh*)LModel->Objects.Items[0];
						E3dPolyGroup*  LPolyGroup = LMesh->PolyGroups.Items[0];

						E3dMaterial*  LMaterial = E3dMaterial_New(_Shader_class);if(LMaterial)
						{
							E_AssignToMember(LMaterial, LPolyGroup,Material);

							E3dThisShader*  LShader = (E3dThisShader*)LMaterial->Shader;
							EpglGenTextures(1, &LShader->GLVoxels);

							if(LShader->GLVoxels)
							{
								const EIndex  LNumOfVoxels = LResolution.X * LResolution.Y * LResolution.Z;

								switch(LPixelFormat.Type)
								{
									case EpGRAY:
										switch(LPixelFormat.ComponentType)
										{
											case EResUINT16:
												{
													printf("%s() %s:%d LShader->GLVoxels %d\n", __FUNCTION__, __FILE__, __LINE__, LShader->GLVoxels);fflush(stdout);
//printf("%s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
													EpGrayu16Pixel*  LDstVoxels = (EpGrayu16Pixel*)LPtr;
													EpGrayu16Pixel*  LDstVoxel = LDstVoxels;
													EIndex  LI = 0;for(;LI < LNumOfVoxels;LI++)
													{
														*LDstVoxel = ((EpGrayu16Pixel)(LPtr[1])) << 8 | ((EpGrayu16Pixel)(LPtr[0]));LPtr += 2;
														*LDstVoxel *= 10;
														LDstVoxel++;
													}
													EpglTexImage3D(GL_TEXTURE_3D, 0, GL_INTENSITY,  LResolution.X, LResolution.Y, LResolution.Z,  0, GL_LUMINANCE, GL_UNSIGNED_SHORT, LDstVoxels);
												}
											break;

											default:
												{
													const int  sl = 256;char  s[sl];
													printf("%s() %s:%d  %sIMPLEMENT ME%s  [%s]\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeYellow, ECodeNormal,  EpPixelFormat_ToString(&LPixelFormat,  s, sl));
													fflush(stdout);
												}
											break;
										}
									break;

									default:
										{
											const int  sl = 256;char  s[sl];
											printf("%s() %s:%d  %sIMPLEMENT ME%s  [%s]\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeYellow, ECodeNormal,  EpPixelFormat_ToString(&LPixelFormat,  s, sl));
											fflush(stdout);
										}
									break;
								}
							}
						}
					}
				}
			}
		}
		else
		{
// Slices are quads
//
			E3dModel*  LModel;E_New(E3dModel, LModel);if(LModel)
			{
				E_Assign(LModel, *PModelRet);  // Assign LModel to the return value, so it's properly reference counted / tracked
				if(P3DFile->Name)
				{
					const char*  LFileName = EStr_GetFileName(P3DFile->Name);
					const int    LL = (int)strlen(LFileName);char*  LName = EMalloc(LL - 4 + 1);
					memcpy(LName, LFileName, LL - 4);LName[LL - 4] = 0;
					EObject_SetName(LModel, EStrDup(LName), FALSE);
				}
				E3dMesh*  LMesh = (E3dMesh*)E3dModel_ObjectAddNew(LModel, E3dMesh_class);

				if(LMesh)
				{
					const E3dCoordinate  LSizeXH = LSampleSpacing.X * (E3dCoordinate)LResolution.X * 0.5, LSizeYH = LSampleSpacing.Y * (E3dCoordinate)LResolution.Y * 0.5;
//					const E3dCoordinate  LSizeXH = LSliceSize.X * 0.5, LSizeYH = LSliceSize.Y * 0.5;
					E3dCoordinate  LZ = 0.0;


					const EIndex  LNumOfVertices = LResolution.Z * 4;
					E3dMesh_VerticesAdd(LMesh, LNumOfVertices);

					E3dVertex*    LVertex = LMesh->Vertices.Items;const E3dVertexSize  LVertexSize = LMesh->VertexSize;

//					EIndex  LZi, LVBase = 0;for(LZi = 0;LZi < LResolution.Z;LZi++, LZ += LSampleSpacing.Z, LVBase += 4)
					EIndex  LZi, LVBase = 0;for(LZi = 0;LZi < LResolution.Z;LZi++, LZ += LSliceSpacing, LVBase += 4)
//					EIndex  LZi, LVBase = 0;for(LZi = 0;LZi < LResolution.Z;LZi++, LZ -= LSliceThickness, LVBase += 4)
					{
						LVertex->Position = E3dCoord3_s(LSizeXH,  LSizeYH, LZ);EStruct_Inc(LVertex, LVertexSize);
						LVertex->Position = E3dCoord3_s(-LSizeXH,  LSizeYH, LZ);EStruct_Inc(LVertex, LVertexSize);
						LVertex->Position = E3dCoord3_s(-LSizeXH, -LSizeYH, LZ);EStruct_Inc(LVertex, LVertexSize);
						LVertex->Position = E3dCoord3_s(LSizeXH, -LSizeYH, LZ);EStruct_Inc(LVertex, LVertexSize);

						E3dPolyGroup*  LPolyGroup = E3dMesh_PolyGroupAdd(LMesh);
						LPolyGroup->DoubleSided = TRUE;
						E3dPolygon*    LPolygon = E3dPolygons_New(1);
						LPolyGroup->Polygons.Items = LPolygon;LPolyGroup->Polygons.Count = LPolyGroup->Polygons.CountAllocated = 1;
						E3dPolygon_InitVa(LPolygon, LPolyGroup->VertexNodeSize, 4,  LVBase + 0, LVBase + 1, LVBase + 2, LVBase + 3);
//						E3dPolygon_InitVa(LPolygon, LPolyGroup->VertexNodeSize, 4,  LVBase + 3, LVBase + 2, LVBase + 1, LVBase + 0);

						E3dMaterial*  LMaterial = E3dMaterial_New(E3dUnlitShader_class);if(LMaterial)
						{
							E_AssignToMember(LMaterial, LPolyGroup,Material);
							{ char  s[256];sprintf(s, "%03d", (int)LZi);EObject_SetName(LMaterial, EStrDup(s), FALSE); }
							E3dUnlitShader*  LShader = (E3dUnlitShader*)(LMaterial->Shader);
							LShader->Diffuse.RGB.R = 0.0;
							LShader->Diffuse.RGB.G = 0.0;
							LShader->Diffuse.RGB.B = 0.0;
							LShader->Transparency = 0.999;

							E3d2DTexture*  LTexture = E3dMaterial_AddTexture(LMaterial);if(LTexture)
							{
								E3dImage*  LImage = (E3dImage*)EpImage_NewC(E3dImage_class,  LResolution.X, LResolution.Y,  LPixelFormat.Type, LPixelFormat.NComponents, LPixelFormat.ComponentType,  0);
								E3d2DTexture_SetImage(LTexture, LImage);

								{
									EIndex  LI = 0;const EIndex  LN = LTexture->Blenders_Count;E3dTextureBlender*  LBlender = LTexture->Blenders;for(;LI < LN;LI++, LBlender++)
									{
										if(EStr_Equal(LBlender->ShaderResource->Name, "Diffuse"))
										{
											LBlender->Factor = 1.0;break;
										}
									}
								}

								EpPixelBuffer*  LDstPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)LImage,  LPixelFormat.Type, LPixelFormat.NComponents, LPixelFormat.ComponentType,  FALSE,  NULL);if(LDstPixelBuffer)
								{
									switch(LPixelFormat.Type)
									{
										case EpGRAY:
											switch(LPixelFormat.ComponentType)
											{
												case EResUINT16:
													{
//printf("%s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
														EpGrayu16Pixel*  LDstPixel = (EpGrayu16Pixel*)LDstPixelBuffer->u8;
														EIndex  LI = 0;for(;LI < LNumOfPixelsPerSlice;LI++)
														{
															*LDstPixel = ((EpGrayu16Pixel)(LPtr[1])) << 8 | ((EpGrayu16Pixel)(LPtr[0]));LPtr += 2;
															*LDstPixel *= 10;
															LDstPixel++;
														}
													}
												break;

												default:
													{
														const int  sl = 256;char  s[sl];
														printf("%s() %s:%d  %sIMPLEMENT ME%s  [%s]\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeYellow, ECodeNormal,  EpPixelFormat_ToString(&LPixelFormat,  s, sl));
														fflush(stdout);
													}
												break;
											}
										break;

										default:
											{
												const int  sl = 256;char  s[sl];
												printf("%s() %s:%d  %sIMPLEMENT ME%s  [%s]\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal, EpPixelFormat_ToString(&LPixelFormat,  s, sl));
												fflush(stdout);
											}
										break;
									}
								}
							}
						}

					}
				}
			}
		}

		EFree(LBuffer);
		return(E_SUCCESS);
	}

	return(E_READ_ERROR);
}


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

	{
		static E3dIOModule  LModelIOModule =
		{
			.Name=                "MVD",
			.StructSize=          sizeof(E3dIOModule),
			.FileNameExtensions=  _FileNameExtensions,
			.FormatCheck=         _FormatCheck,

			.Load=                (E3dLoadFunc)_ModelLoad
		};
		_IOModule = E3d_3DModelIOModuleAdd(&LModelIOModule);
	}

	{
		E3dShaderClass  LShaderClass=
		{
			.Name=        "Voxels",
			.StructSize=  sizeof(E3dThisShader),
//		.Resources=   _Resources,
			.Init=        _ShaderInit,
			.Update=      _ShaderUpdate,

			.GLDraw=      E3dGL_DrawPhong
		};
		_Shader_class = E3dShader_ClassRegister(&LShaderClass);
	}

// Add GLSL binding
//
	{
		E3dRendererClass*  LRendererClass = E3dRenderer_ClassByName("GLSL");if(LRendererClass)
		{
			E3dShaderClassRendererBinding  LDescriptor=
			{
				.ShaderBindingStructSize=  sizeof(E3dShaderRendererBinding),
				.Resources=                _SL_Resources,
			};
			E3dShaderClassRendererBinding*  LShaderClassBinding = E3dShader_ClassAddRenderer(_Shader_class, LRendererClass, &LDescriptor);

			if(LShaderClassBinding) LShaderClassBinding->SourceCore = Examples_Voxels_frg_glsl;

			EIndex  LI = 0;const EIndex  LN = _SL_Resources.Count;EResource*  LResource = _SL_Resources.Items;for(;LI < LN;LI++, LResource++)
			{
				if(LResource->Name && EStr_Equal(LResource->Name, "Voxels")) { LResource->Ext.Ptr = LRendererClass->ShaderDevice->RT_sampler3D;break; }
			}

		}
	}


	return(E_SUCCESS);
}


//========================================
// Exit method of the plugin
//========================================
int E_PluginExit(const EBool PExiting)
{
	if(!PExiting || Xe_GUIFreeOnExit)
	{
		XeItemFree(_MenuButton);
		XeItemFree(_ToolBarButton);
	}

	if(_IOModule)
	{
		E3d_3DModelIOModuleDeactivate(_IOModule);_IOModule = NULL;
	}
	if(_Shader_class)
	{
		E3dShader_ClassDeactivate(_Shader_class);_Shader_class = NULL;
	}

	EpIconFree3(Grid);

	return(E_SUCCESS);
}
