//================================================================
// E3D library
//
// Textures
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Jul-08 04:06:22
//
// Copyright (C) 1996-2026 by Gabor Nagy. All rights reserved.
//================================================================
#ifndef _E3D_Texture_h
#define _E3D_Texture_h

#include <assert.h>
#include <Image/Image.h>

#ifndef _E3D_Geometry_h
#include <E3D/Geometry.h>
#endif

#define E3dST_OFF_TEXTURE        -1000000.0
#define E3dMAX_TEXTURE_CHANNELS  4  // Lock this down to "RGBA" until we support hyperspectral textures

// Texture update flags
//
#define E3dTxtUF_FILTERING  0x0001
#define E3dTxtUF_IMAGE      0x0002
#define E3dTxtUF_NORMALMAP  0x0004
#define E3dTxtUF_CDF        0x0008
#define E3dTxtUF_BLENDING   0x0010
#define E3dTxtUF_ALL        0x001F

//----------------------------------------------------------------
// 2D texture old feature flags used by the Softimage I/O plugin. 
//----------------------------------------------------------------
#define	E3dTXTF_SWAP_ST             0x01
#define	E3dTXTF_ALTERNATE_S         0x02
#define	E3dTXTF_ALTERNATE_T         0x03
#define	E3dTXTF_TRANSPARENCYBW      0x08
#define	E3dTXTF_CROPPING            0x10
#define	E3dTXTF_ALL                 0x1F

#define	E3dTextureMapperFROM_MESH   0x8000


#if defined (__cplusplus) && (!(defined (__CPLUSLIBS)))
extern "C" {
#endif

typedef struct E3d2DTexture_S             E3d2DTexture;
typedef struct E3d2DTexture_S*            E3d2DTexturePtr;
EList_t(E3d2DTexturePtr);
EListA_t(E3d2DTexturePtr);

#ifndef E3dTextureMapperDEF
#define E3dTextureMapperDEF  1
typedef struct E3dTextureMapper_S         E3dTextureMapper;
typedef struct E3dTextureMapper_S*        E3dTextureMapperPtr;
EList_t(E3dTextureMapperPtr);
EListA_t(E3dTextureMapperPtr);
#endif

#define E3dTextureMapperFreehand          E3dTextureMapper

typedef struct E3d2DTextureShaderClass_S  E3d2DTextureShaderClass;
typedef struct E3d2DTextureShader_S       E3d2DTextureShader;


typedef int  (*E3d2DTextureShaderGUIFunc)(E3d2DTexture* PTexture, void* PTexturePanel, void* PTextureShaderClassGUI, const int PAction);
typedef int  (*E3d2DTextureShaderGetImageFunc)(E3dScene* PScene, E3d2DTexture* PTexture, const E3dTextureMapper* PMapper,  const EIndex PX, const EIndex PY,  const EIndex PSizeX, const EIndex PSizeY,  EpImage** PImage);


struct E3d2DTextureShaderClass_S
{
	EBaseClassCore(E3d2DTextureShader);

	E3d2DTextureShaderGUIFunc       GUI;
	E3d2DTextureShaderGetImageFunc  GetImage;
};


#define E3d2DTextureShaderCore\
 E3d2DTextureShaderClass*  Class;\
 EBool                     UsesFiles, Animated, Animated_AbsoluteTime, Animated_Streaming

struct E3d2DTextureShader_S
{
	E3d2DTextureShaderCore;
};



enum
{
	E3dTextureFilterNEAREST = 0,
	E3dTextureFilterLINEAR
};


enum
{
	E3dTANGENT_SPACE = 1, E3dOBJECT_SPACE, E3dWORLD_SPACE
};


typedef struct E3dTextureBlender_S  E3dTextureBlender;

struct E3dTextureBlender_S
{
	EResource*  ShaderResource;
	float       Factor;     // Scaler
	uint32_t    Channels;   // EpRED, EpGREEN, EpBLUE, EpALPHA, EpINTENSITY
	uint16_t    Operation;  // E3dADD, E3dMUL

	void        (*Modulate)(const EcRGBAf32Color PTextureColor, const E3dTextureBlender* PBlender, void* PDst);
};

enum
{
	E3dTilingCLAMP = 1,
	E3dTilingREPEAT,
	E3dTilingALTERNATE
};


typedef void  (*E3dTextureSamplerFunc)(const E3d2DTexture* PTexture, const E3dSTWithDiff* PST,  const char PTilingModeS, const char PTilingModeT,  E3dSurfacePointBase* PPoint, float* PResult);

#define E3dImageCDFSampleCore\
	float    CDFPosition;     /* CDF range start                   */\
	EIndex2  PixelPosition    /* Pixel coordinates in the texture  */


// 2D cumulative distribution function entry
typedef struct
{
	E3dImageCDFSampleCore;
} E3dImageCDFSample;


// 3D cumulative distribution function entry
typedef struct
{
	E3dImageCDFSampleCore;

	E3dCoord3  Position, Normal;  // Position of sample in 3D
	float      Color[3];
} E3d3DCDFSample;
EListA_t(E3d3DCDFSample);

typedef struct
{
	EListA_s(, E3d3DCDFSample, Samples);
} E3d3DCDF;


typedef struct
{
	EpImageCore;
	EpPixelBuffer*  PixelBufferRGBf32;
	EpPixelBuffer*  PixelBufferRGBA8;
	EpPixelBuffer*  PixelBufferRGBX8;
	EpPixelBuffer*  PixelBufferGRAY8;

	double  TimeStamp;  // For animated Textures
} E3dImage, *E3dImagePtr;
EListA_t(E3dImagePtr);


#ifdef USEOpenGL

#include <Image/GL/EpGL.h>

#ifndef EpGLTexture_DEF
#define EpGLTexture_DEF
typedef struct EpGLTexture_S  EpGLTexture;
#endif

#endif // USEOpenGL


struct E3d2DTexture_S
{
	EObjectCore(EObject);

	EObjectID       ID;                   // Unique ID of this Texture. This ensures that we can identify Textures clearly, even if it we free Objects and a freed pointer gets reused for a new allocation.
	EObjectID       OriginalID;           // The ID of the Texture this one was cloned from. 0 if this is not a clone.

	double                 ImageLoadedForThisFrame;  // If the texture is animated (has a % character in the file name to indicate a printf-style number for the frame), the image was loaded for this frame

	E3d2DTextureShader*    Shader;             // Shader that generates the image. The default is NULL, meaning an image file specified by the FileName field below

	char*                  FileName;           // Texture file name with path
	char*                  NormalMapFileName;  // NormalMap file name with path
	uint16_t               ColorSpace;         // EcLINEAR, EcsRGB, EcAdobeRGB etc.

	E3dTextureSamplerFunc  Sample;             // Sampling function, set by E3d2DTexture_UpdateForDisplay

	EChangeFlags           ChangedFlags;
	EChanges               Changes;

	E3dImage*              Image;              // E3dImage of the texture

// For animated textures
//
	EListA(, E3dImage*, ImageFrames);
	EIndex                 CurrentFrame;

	EListA_s(, E3dImagePtr, MIPLevels);
	EBool                  UseMIPMaps, AnisotropicFiltering;

// E3dFIXME Temporary for reading some old files
//
	E3dTextureMapper*      MapperOld;
	uint16_t               FlagsOld;           // Mapping info for old Softimage files. Swap ST, ST alternation, crop etc.

	EInt32_2               CropMin, CropMax;   // Cropping info


// Application of the Texture
//
	EList(, E3dTextureBlender, Blenders);      // One for each shader Resource

// Special blenders that don't map to Shader Resources
//
	E3dTextureBlender      Bump;               // Blender for bump-mapping
	E3dTextureBlender      Displacement;       // Blender for displacement-mapping

	EpImage*               NormalMapImage;        // For bump-mapping/normal-mapping.
	EpPixelBuffer*         NormalMapPixelBuffer;  // The PixelBuffer used by the CPU ray-tracer

	EBool                  ImageAsNormalMap;   // Use RGB image directly as a normal-map

	E3dCoord3              SchemPosition;      // Position on schematic view
	uint8_t                NormalMapType,      // Use RGB image directly as a normal-map 0, E3dTANGENT_SPACE, E3dOBJECT_SPACE or E3dWORLD_SPACE
	                       MagFilter;          // Magnifying filter


// Internals for rendering
//
	uint32_t               DoNormalMap : 1, DoTangentSpace : 1,
	                       NormalMapPixelFormat : 10;   // EpPixelFormatRGBf32 etc.
	uint32_t               ReflectiveChannelMask, TransparentChannelMask;
	int                    MIPIndexDiff;

	int16_t                CDFImageSizeDivider;   // Scale down the Texture->Image by this factor, for building the CDF (may improve rendering performance and in soft IBL, the full resolution may not be needed)
	uint16_t               CDFImageBlurRadius;    // Blur radius passed to EpImage_Scale. Fixed point x100
	EpImage*               CDFRefImage;           // The image from which the CDF is built. May have a lower resolution than Texture->Image (and may be blurred), to minimize artifacts and to save memory.
	E3dImageCDFSample*     CDF;                   // Cumulative Distribution Function for emission Textures, for importance-sampling textured area lights.
	float                  EmissionFactor;        // Store it here as well, so we don't have to look it up when sampling the area light...

	EBool                  Selected;


#ifdef USEOpenGL
	EpGLTexture*           GLTexture_2D;       // For 2D textures
	EpGLTexture*           GLTexture_CUBE;     // For cube-maps

	int                    GLAlphaFunction;    // GL_ALWAYS means alpha-testing disabled
	float                  GLAlphaReference;

	GLuint                 GLNormalMapIndex;
	uint32_t               GLNormalMapValid : 1, GLNormalMapUploaded : 1;
	EListA_s(, E3dImagePtr, GLMIPLevels);
#endif // USEOpenGL
};


enum
{
	E3dTXMAP_NONE = 0,
	E3dTXMAP_POLYGROUP,
	E3dTXMAP_POLYGONS,	// Selected Polygons
	E3dTXMAP_GEOMETRY,
	E3dTXMAP_MODEL,
	E3dTXMAP_BRANCH,
	E3dTXMAP_TREE
};

typedef struct
{
	E3dCoord3  Position;
	E3dCoord3  Origin, Direction;E3dCoordinate  Distance;  // Returned by the Spherical Mapper, Cubemap Mapper etc.
	float      Density, Weight;
} E3dTextureMapperSTTo3DStruct;

typedef int  (*E3dTextureMapperUpdateFunc)(E3dTextureMapper* PMapper,  const E3dVertex* PVertices, const EIndex PNVertices, const E3dVertexSize PVertexSize,  const EObject* PShadeable);
typedef int  (*E3dTextureMapper3DToSTFunc)(const E3dTextureMapper*, const E3dCoord3* P3DPosition, const E3dCoord3* PNormal, E3dST* PST);
typedef int  (*E3dTextureMapperSTTo3DFunc)(const E3dTextureMapper*, const E3dST* PST,  E3dTextureMapperSTTo3DStruct* PResult);

typedef struct E3dTextureMapperClass_S  E3dTextureMapperClass;


struct E3dTextureMapperClass_S
{
	EBaseClassCore(E3dTextureMapper);

	E3dTextureMapperUpdateFunc  Update;       // Called when TextureMapper parameters or the Geometry change
	E3dTextureMapper3DToSTFunc  MapPoint;     // 3D point -> texture coordinates
	E3dTextureMapperSTTo3DFunc  InvMapPoint;  // Texture coordinates -> 3D point
	ECoreObjectGUIFunc          GUI;
};

typedef struct E3dGLVertex_S  E3dGLVertex;

typedef struct
{
	E3dGLVertex*   GLArrayVertices;        // GPU vertex array, used for drawing the texture-space Vertices and Edges
	EIndex         GLArrayVerticesSize;    // Size of the above vertex array in bytes

	unsigned int*  GLArrayUnselectedEdgeVertexIndices;EIndex  GLArrayUnselectedEdgesCount;
	unsigned int*  GLArraySelectedEdgeVertexIndices;EIndex  GLArraySelectedEdgesCount;

	uint32_t       GLArrayVerticesValid : 1, GLArrayEdgeIndicesValid : 1;
} E3dTextureMapperGPUData;

#define E3dTextureMapperCore\
	EObjectCore(E3dTextureMapper);\
\
	EList(, E3dEdge, Edges);                               /* For ST editing : Edges in texture-space  */\
	uint16_t*         PolygonFlags;EIndex	Polygons_Count;  /* For ST editing :  "Vertex" flags for each Polygon VertexNode ST, for the ST set corresponding to this Mapper. Layout : [Polygons_Count]                    */\
	uint16_t**        VertexFlags;                         /* For ST editing :  "Vertex" flags for each Polygon VertexNode ST, for the ST set corresponding to this Mapper. Layout : [Polygons_Count][NumOfVertexNodes]  */\
\
\
/* User settable  */\
\
	uint8_t           FrontBack;             /* Project on E3dFRONT and/or E3dBACK              */\
	E3dCoord3         Translation;           /* Translation of texture (SOffset, TOffset, 0.0)  */\
\
	E3dCoord3         Scaling;               /* Scaling of texture (SScaling, TScaling, 1.0)  */\
	EBool             ScalingKeepRatio;      /* Keep ratio between S and T scaling as one of them is changed by the user */\
\
	E3dEulerRotation  EulerRotation;         /* Rotation of texture  */\
	E3dCoord2         STScaling;             /* Size of application zone in S and T  */\
	E3dCoord2         STOffset;              /* Offset of application zone in S and T  */\
	EInt32_2          STCount;               /* Repetition of image in S and T  */\
	EBool             AlternateS, AlternateT;\
\
	int32_t           BRDFSamplesSqrt;       /* Sample the BRDF to get accurate specular lighting when used with reflection cube-maps. */\
	float             EnvLightFactor;\
\
\
/* Internal	*/\
\
	int8_t            TilingS, TilingT;      /* Tiling mode : E3dTilingCLAMP, E3dTilingREPEAT or E3dTilingALTERNATE  */\
\
	int               MapOn;                 /* Mapping target (PolyGroup, Mesh, Model, Branch etc)  */\
	E3dGeometry*      ProjectorGeometry;\
	E3dCoord3         ProjectorCenter;       /* Center of texture projector Geometry  */\
	E3dMatrix         ProjectorMatrix,       /* Matrix for drawing the texture-projector Geometry  */\
										LocalToMapperMatrix,\
										WorldToMapperMatrix, WorldToMapperNormalMatrix,\
										MapperToWorldMatrix, MapperToWorldNormalMatrix;\
	E3d3DCDF          Emission3DCDF;         /* Cached Cumulative Distribution Function, used if this is an emission Texture  */\
  E3dTextureMapperGPUData  GPUData;\
	EBool             Selected

// This gets attached to Shadeables (PolyGroup, Face etc.)
//
struct E3dTextureMapper_S
{
	E3dTextureMapperCore;
};


typedef struct
{
	E3dTextureMapperCore;

	E3dCoord3  Min, Max;     // Projector Geometry's boundaries
} E3dTextureMapperPlanar;


typedef struct
{
	E3dTextureMapperCore;

	EBool  SwapUV;
} E3dTextureMapperUV;


typedef struct
{
	E3dCoord3      Center;    // Center of minimal enclosing sphere
	E3dCoordinate  Radius;    // Radius of minimal enclosing sphere
} E3dWireSphere;


#define E3dMinSphereBasedTextureMapperCore\
	E3dTextureMapperCore;\
\
	E3dWireSphere	Sphere

typedef struct
{
	E3dMinSphereBasedTextureMapperCore;
} E3dMinSphereBasedTextureMapper;

extern int  E3d_MinSphereBasedTextureMapperUpdate(const E3dVertex* PVertices, const EIndex PNVertices, const E3dVertexSize PVertexSize,  E3dMinSphereBasedTextureMapper* PMapper);


//------------------------------------------------------------------------


//----------------------------------------
// 3D texture
//----------------------------------------
typedef struct
{
	int        RefCnt;           // Reference count
	char*      Name;
	char*      FileName;
	E3dCoord3  SchemPosition;    // Position on schematic view
} E3d3DTexture;
//------------------------------------------------------------------------


static inline EBool E3dTexture_IsBumpmap(E3d2DTexture* PTexture)
{
	if(PTexture->Bump.Factor > 0.001f || PTexture->Bump.Factor < -0.001f) return(TRUE);
	else return(FALSE);
}

static inline EBool E3dTexture_IsDisplacementmap(E3d2DTexture* PTexture)
{
	if(PTexture->Displacement.Factor > 0.001f || PTexture->Displacement.Factor < -0.001f) return(TRUE);
	else return(FALSE);
}


#define _E3dM_NoTextureCoverage(pRGBAa)  do { pRGBAa[0] = pRGBAa[1] = pRGBAa[2] = pRGBAa[3] = 0.0; } while(0)\


//
// Apply tiling to a coordinate, using a modulo
//
#define _M_Window(pVal, pModulo)     { if(pVal < 0) { pVal = pModulo + ((pVal - 1) % pModulo);if(pVal >= pModulo) pVal = 0; } else pVal = pVal % pModulo; }
#define _M_Alternate(pVal, pModulo)  { const EIndex  lVal = pVal;if(pVal < 0) pVal = pModulo + (pVal % pModulo);else pVal = pVal % pModulo;if((lVal / pModulo) & 1) pVal = pModulo - 1 - pVal; }
#define _M_Clamp(pVal, pModulo)      { if(pVal < 0) pVal = 0;else if(pVal >= pModulo) pVal = pModulo - 1; }


#define _E3d_Nearest1(pType, pComponentsPerPixel, pStride) do\
{\
	const pType*  lSrcPixels = (pType*)LSrcPixelBuffer->u8;\
	const pType*  lSrcPixel = lSrcPixels + (LTX + LTY * LSizeX) * pStride;\
\
	int  lI = 0;for(;lI < pComponentsPerPixel;lI++)\
	{\
		PResult[lI] = (float)lSrcPixel[lI];\
	}\
} while(0)


#define _E3d_Nearest1_n(pType, pComponentsPerPixel, pStride, pNormalizer) do\
{\
	const pType*  lSrcPixels = (pType*)LSrcPixelBuffer->u8;\
	const pType*  lSrcPixel = lSrcPixels + (LTX + LTY * LSizeX) * pStride;\
\
	int  lI = 0;for(;lI < pComponentsPerPixel;lI++)\
	{\
		PResult[lI] = (float)(lSrcPixel[lI]) * pNormalizer;\
	}\
} while(0)


static inline void _E3d_ImageColorNearest_AUTO_RGBAf32(const E3dImage* PImage,  const E3dCoordinate PFX, const E3dCoordinate PFY,  const char PTilingModeS, const char PTilingModeT,  float* PResult)
{
	const EIndex  LSizeX = PImage->Size.X, LSizeY = PImage->Size.Y;
	EIndex        LTX = (EIndex)PFX, LTY = (EIndex)PFY;

	switch(PTilingModeS)
	{
		case E3dTilingREPEAT:
			_M_Window(LTX, LSizeX);
		break;

		case E3dTilingALTERNATE:
			_M_Alternate(LTX, LSizeX);
		break;

		default:
			_M_Clamp(LTX, LSizeX);
		break;
	}

	switch(PTilingModeT)
	{
		case E3dTilingREPEAT:
			_M_Window(LTY, LSizeY);
		break;

		case E3dTilingALTERNATE:
			_M_Alternate(LTY, LSizeY);
		break;

		default:
			_M_Clamp(LTY, LSizeY);
		break;
	}

assert(LTX >= 0);
assert(LTX < LSizeX);
assert(LTY >= 0);
assert(LTY < LSizeY);

	const EpPixelBuffer*  LSrcPixelBuffer;
	if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpRGBA, 0, EResFLOAT32,  FALSE,  NULL)) != NULL)
	{
		_E3d_Nearest1(float, 4, 4);
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpRGBA, 0, EResUINT16,  FALSE,  NULL)) != NULL)
	{
		_E3d_Nearest1_n(uint16_t, 4, 4, EInv65536N);
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpRGBA, 0, EResUINT8,  FALSE,  NULL)) != NULL)
	{
		_E3d_Nearest1_n(uint8_t, 4, 4, EInv256N);
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpRGB, 0, EResFLOAT32,  FALSE,  NULL)) != NULL)
	{
		_E3d_Nearest1(float, 3, 3);
		PResult[3] = 1.0;
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpRGB, 0, EResUINT16,  FALSE,  NULL)) != NULL)
	{
		_E3d_Nearest1_n(uint16_t, 3, 3, EInv65536N);
		PResult[3] = 1.0;
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpRGB, 4, EResUINT8,  FALSE,  NULL)) != NULL)
	{
		_E3d_Nearest1_n(uint8_t, 3, 4, EInv256N);
		PResult[3] = 1.0;
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpRG, 0, EResUINT8,  FALSE,  NULL)) != NULL)
	{
		_E3d_Nearest1_n(uint8_t, 2, 2, EInv256N);
		PResult[2] = 0.0;
		PResult[3] = 1.0;
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpGRAYA, 0, EResUINT16,  FALSE,  NULL)) != NULL)
	{
		_E3d_Nearest1_n(uint16_t, 2, 2, EInv65536N);
		PResult[3] = PResult[1];
		PResult[1] = PResult[2] = PResult[0];
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpGRAYA, 0, EResUINT8,  FALSE,  NULL)) != NULL)
	{
		_E3d_Nearest1_n(uint8_t, 2, 2, EInv256N);
		PResult[3] = PResult[1];
		PResult[1] = PResult[2] = PResult[0];
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpGRAY, 0, EResUINT16,  FALSE,  NULL)) != NULL)
	{
		_E3d_Nearest1_n(int16_t, 1, 1, EInv65536N);
		PResult[1] = PResult[2] = PResult[0];
		PResult[3] = 1.0;
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpGRAY, 0, EResINT16,  FALSE,  NULL)) != NULL)
	{
		_E3d_Nearest1_n(int16_t, 1, 1, EInv32768N);
		PResult[1] = PResult[2] = PResult[0];
		PResult[3] = 1.0;
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpGRAY, 0, EResUINT8,  FALSE,  NULL)) != NULL)
	{
		_E3d_Nearest1_n(uint8_t, 1, 1, EInv256N);
		PResult[1] = PResult[2] = PResult[0];
		PResult[3] = 1.0;
	}
	else
	{
		{
			const int  sl = 4096;char  s[sl];
			printf("%s() %s:%d  %sIMPLEMENT ME%s pixel formats %s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal, EpImage_PixelFormatsToString((EpImage*)PImage,  s, sl));
			fflush(stdout);
		}
		PResult[0] = 0.0;
		PResult[1] = 0.0;
		PResult[2] = 0.0;
		PResult[3] = 1.0;
	}
}


//========================================
// Setup step for bi-linear filtering
//========================================
#define _E3d_Bilinear0(pX, pY,  pTilingS, pTilingT) do\
{\
	LT.X = (EIndex)(pX);\
	LFraction.X = pX >= 0.0 ? pX - (E3dCoordinate)LT.X : 1.0 - ((E3dCoordinate)LT.X - pX);\
	if(LSizeX > 1)\
	{\
		if(LFraction.X < 0.5) LNeighbor.X = LT.X - 1;else LNeighbor.X = LT.X + 1;\
	}\
	else LNeighbor.X = LT.X;\
	LFractionInv.X = E3d_Abs((E3dCoordinate)0.5 - LFraction.X);LFraction.X = 1.0 - LFractionInv.X;\
\
\
	LT.Y = (EIndex)(pY);\
	LFraction.Y = pY >= 0.0 ? pY - (E3dCoordinate)LT.Y : 1.0 - ((E3dCoordinate)LT.Y - pY);\
	if(LSizeY > 1)\
	{\
		if(LFraction.Y < 0.5) LNeighbor.Y = LT.Y - 1;else LNeighbor.Y = LT.Y + 1;\
	}\
	else LNeighbor.Y = LT.Y;\
\
	LFractionInv.Y = 0.5 - LFraction.Y;LFractionInv.Y = E3d_Abs(LFractionInv.Y);LFraction.Y = 1.0 - LFractionInv.Y;\
\
	switch(pTilingS)\
	{\
		case E3dTilingREPEAT:\
			_M_Window(LNeighbor.X, LSizeX);\
			_M_Window(LT.X, LSizeX);\
		break;\
\
		case E3dTilingALTERNATE:\
			_M_Alternate(LNeighbor.X, LSizeX);\
			_M_Alternate(LT.X, LSizeX);\
		break;\
\
		default:\
			_M_Clamp(LNeighbor.X, LSizeX);\
			_M_Clamp(LT.X, LSizeX);\
		break;\
	}\
\
	switch(pTilingT)\
	{\
		case E3dTilingREPEAT:\
			_M_Window(LNeighbor.Y, LSizeY);\
			_M_Window(LT.Y, LSizeY);\
		break;\
\
		case E3dTilingALTERNATE:\
			_M_Alternate(LNeighbor.Y, LSizeY);\
			_M_Alternate(LT.Y, LSizeY);\
		break;\
\
		default:\
			_M_Clamp(LNeighbor.Y, LSizeY);\
			_M_Clamp(LT.Y, LSizeY);\
		break;\
	}\
} while(0)

#define _E3d_Bilinear1(pType, pComponentsPerPixel, pStride, pT, pNeighbor, pFraction, pFractionInv) do\
{\
	const pType*  lSrcPixels = (pType*)LSrcPixelBuffer->u8;\
	const pType*  lSrcPixel = lSrcPixels + (pT.X + pT.Y * LSizeX) * pStride;\
	const pType*  lSrcPixelXNeighbor = lSrcPixels + (pNeighbor.X + pT.Y * LSizeX) * pStride;\
	const pType*  lSrcPixelYNeighbor = lSrcPixels + (pT.X + pNeighbor.Y * LSizeX) * pStride;\
	const pType*  lSrcPixelXYNeighbor = lSrcPixels + (pNeighbor.X + pNeighbor.Y * LSizeX) * pStride;\
\
	int  lI = 0;for(;lI < pComponentsPerPixel;lI++)\
	{\
		const float  LV  = (float)(lSrcPixel[lI]) * pFraction.X + (float)(lSrcPixelXNeighbor[lI]) * pFractionInv.X;\
		const float  LV1 = (float)(lSrcPixelYNeighbor[lI]) * pFraction.X + (float)(lSrcPixelXYNeighbor[lI]) * pFractionInv.X;\
		PResult[lI] = (LV * pFraction.Y + LV1 * pFractionInv.Y);\
	}\
} while(0)


#define _E3d_Bilinear1_Normalize(pType, pComponentsPerPixel, pStride, pNormalizer,  pT, pNeighbor, pFraction, pFractionInv) do\
{\
	const pType*  lSrcPixels = (pType*)LSrcPixelBuffer->u8;\
	const pType*  lSrcPixel = lSrcPixels + (pT.X + pT.Y * LSizeX) * pStride;\
	const pType*  lSrcPixelXNeighbor = lSrcPixels + (pNeighbor.X + pT.Y * LSizeX) * pStride;\
	const pType*  lSrcPixelYNeighbor = lSrcPixels + (pT.X + pNeighbor.Y * LSizeX) * pStride;\
	const pType*  lSrcPixelXYNeighbor = lSrcPixels + (pNeighbor.X + pNeighbor.Y * LSizeX) * pStride;\
\
	int  lI = 0;for(;lI < pComponentsPerPixel;lI++)\
	{\
		const float  LV  = (float)(lSrcPixel[lI]) * pFraction.X + (float)(lSrcPixelXNeighbor[lI]) * pFractionInv.X;\
		const float  LV1 = (float)(lSrcPixelYNeighbor[lI]) * pFraction.X + (float)(lSrcPixelXYNeighbor[lI]) * pFractionInv.X;\
		PResult[lI] = (LV * pFraction.Y + LV1 * pFractionInv.Y) * pNormalizer;\
	}\
} while(0)


static inline void _E3d_ImageColorBilinear_AUTO_RGBAf32(const E3dImage* PImage,  const EIndex2 PT,
 const E3dCoord2 PFraction, const E3dCoord2 PFractionInv,
 const EIndex2 PNeighbor,
 float* PResult)
{
	const EIndex    LSizeX = PImage->Size.X;

	const EpPixelBuffer*  LSrcPixelBuffer;
	if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpRGBA, 0, EResFLOAT32,  FALSE,  NULL)) != NULL)
	{
		_E3d_Bilinear1(float, 4, 4, PT, PNeighbor, PFraction, PFractionInv);
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpRGBA, 0, EResUINT16,  FALSE,  NULL)) != NULL)
	{
		_E3d_Bilinear1_Normalize(uint16_t, 4, 4, EInv65536N,  PT, PNeighbor, PFraction, PFractionInv);
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpRGBA, 0, EResUINT8,  FALSE,  NULL)) != NULL)
	{
		_E3d_Bilinear1_Normalize(uint8_t, 4, 4, EInv256N,  PT, PNeighbor, PFraction, PFractionInv);
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpRGB, 0, EResFLOAT32,  FALSE,  NULL)) != NULL)
	{
		_E3d_Bilinear1(float, 3, 3, PT, PNeighbor, PFraction, PFractionInv);
		PResult[3] = 1.0;
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpRGB, 0, EResUINT16,  FALSE,  NULL)) != NULL)
	{
		_E3d_Bilinear1_Normalize(uint16_t, 3, 3, EInv65536N,  PT, PNeighbor, PFraction, PFractionInv);
		PResult[3] = 1.0;
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpRGB, 4, EResUINT8,  FALSE,  NULL)) != NULL)
	{
		_E3d_Bilinear1_Normalize(uint8_t, 3, 4, EInv256N,  PT, PNeighbor, PFraction, PFractionInv);
		PResult[3] = 1.0;
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpRG, 0, EResUINT8,  FALSE,  NULL)) != NULL)
	{
		_E3d_Bilinear1_Normalize(uint8_t, 2, 2, EInv256N,  PT, PNeighbor, PFraction, PFractionInv);
		PResult[2] = 0.0;
		PResult[3] = 1.0;
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpGRAYA, 0, EResFLOAT32,  FALSE,  NULL)) != NULL)
	{
		_E3d_Bilinear1(float, 2, 2, PT, PNeighbor, PFraction, PFractionInv);
		PResult[3] = PResult[1];
		PResult[1] = PResult[2] = PResult[0];
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpGRAYA, 0, EResUINT16,  FALSE,  NULL)) != NULL)
	{
		_E3d_Bilinear1_Normalize(uint16_t, 2, 2, EInv65536N,  PT, PNeighbor, PFraction, PFractionInv);
		PResult[3] = PResult[1];
		PResult[1] = PResult[2] = PResult[0];
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpGRAYA, 0, EResUINT8,  FALSE,  NULL)) != NULL)
	{
		_E3d_Bilinear1_Normalize(uint8_t, 2, 2, EInv256N,  PT, PNeighbor, PFraction, PFractionInv);
		PResult[3] = PResult[1];
		PResult[1] = PResult[2] = PResult[0];
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpGRAY, 0, EResFLOAT32,  FALSE,  NULL)) != NULL)
	{
		_E3d_Bilinear1(float, 1, 1, PT, PNeighbor, PFraction, PFractionInv);
		PResult[1] = PResult[2] = PResult[0];
		PResult[3] = 1.0;
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpGRAY, 0, EResUINT16,  FALSE,  NULL)) != NULL)
	{
		_E3d_Bilinear1_Normalize(uint16_t, 1, 1, EInv65536N,  PT, PNeighbor, PFraction, PFractionInv);
		PResult[1] = PResult[2] = PResult[0];
		PResult[3] = 1.0;
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpGRAY, 0, EResINT16,  FALSE,  NULL)) != NULL)
	{
		_E3d_Bilinear1_Normalize(int16_t, 1, 1, EInv32768N,  PT, PNeighbor, PFraction, PFractionInv);
		PResult[1] = PResult[2] = PResult[0];
		PResult[3] = 1.0;
	}
	else if((LSrcPixelBuffer = EpImage_PixelBufferByFormat((EpImage*)PImage,  EpGRAY, 0, EResUINT8,  FALSE,  NULL)) != NULL)
	{
		_E3d_Bilinear1_Normalize(uint8_t, 1, 1, EInv256N,  PT, PNeighbor, PFraction, PFractionInv);
		PResult[1] = PResult[2] = PResult[0];
		PResult[3] = 1.0;
	}
	else
	{
{
	const int  sl = 4096;char  s[sl];
	printf("%s() %s:%d  %sIMPLEMENT ME%s pixel formats %s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal, EpImage_PixelFormatsToString((EpImage*)PImage,  s, sl));
	fflush(stdout);
}
		PResult[0] = 0.0;
		PResult[1] = 0.0;
		PResult[2] = 0.0;
		PResult[3] = 1.0;
	}
}


EList_i32(extern, E3dTextureMapperClass*, E3d_TextureMapperClasses);


//----------------------------------------
// Global variables
//----------------------------------------
extern EObjectClass  E3d2DTexture_class[];

extern E3dImage*     E3d_NotFound2DTextureImage;
extern EResourceList  E3dTexture_resources;
extern EResourceList  E3d_TextureOutputs;

extern E3dTextureMapper*  E3d_FallbackTextureMapper;


//----------------------------------------
// E3D/Texture.c
//----------------------------------------
EList(extern, E3d2DTextureShaderClass*, E3d2DTextureShader_Classes);
extern EpImageClass  E3dImage_class[];


extern void             E3d2DTexture_Print(E3d2DTexture* P2DTexture, const int PIndent);

extern void             E3d2DTexture_Init(E3d2DTexture* P2DTexture);
extern E3d2DTexture*    E3d2DTexture_New(void);
extern E3d2DTexture*    E3d2DTexture_Copy(const E3d2DTexture* PTexture, const EBool PCopyCaches,  E3d2DTexture** P2DTextures, const EIndex PNumOfTextures);
extern void             E3d2DTexture_CopyContents(const E3d2DTexture* PSrcTexture, E3d2DTexture* PDstTexture, const EBool PCopyCaches);
extern void             E3d2DTexture_SetImage(E3d2DTexture* PTexture, E3dImage* PImage);

extern int       E3d_CreateMIPMaps(const EpImage* PImage0,  EpImageClass* PClass, EpImagePtrListA* PMIPLevels,  const EIndex PMinSizeX, const EIndex PMinSizeY);
extern void      E3d2DTexture_CreateCDF(E3d2DTexture* PTexture);


extern E3d2DTextureShaderClass*  E3d2DTextureShader_ClassRegister(E3d2DTextureShaderClass* PDescriptor);
extern void                      E3d2DTextureShader_ClassDeactivate(E3d2DTextureShaderClass* PClass);
extern E3d2DTextureShaderClass*  E3d2DTextureShader_ClassByName(const char* PName);
extern void                      E3d2DTextureShader_ClassesFree(void);

extern int     E3d2DTexture_BumpMapToNormalMap(E3d2DTexture* PTexture);
extern void    E3d2DTexture_UpdateNormalMap(E3d2DTexture* PTexture);

extern E3d2DTextureShader*  E3d2DTextureShader_New(E3d2DTextureShaderClass* PClass);
extern void                 E3d2DTextureShader_Free(E3d2DTextureShader* PTextureShader);



//----------------------------------------
// TextureMapping.c
//----------------------------------------
extern E3dTextureMapperClass*  E3d_BuiltInTextureMapperClasses[];

extern const char  E3dSemantic_TextureMapping[];

extern void  E3dTextureMapper_CallUpdate(E3dTextureMapper* PMapper, const E3dVertex* PVertices, const EIndex PNVertices, const E3dVertexSize PVertexSize, const EObject* PShadeable);

extern void  E3dTextureMapper_SetTiling(E3dTextureMapper* PMapper);

extern E3dTextureMapperClass  E3dTextureMapper_class[];
extern E3dTextureMapperClass  E3dTextureMapperFreehand_class[];



//----------------------------------------
// Adv/TextureMappingAdv.c
//----------------------------------------
extern E3dTextureMapperClass  E3dTextureMapperXY_class[];
extern E3dTextureMapperClass  E3dTextureMapperXZ_class[];
extern E3dTextureMapperClass  E3dTextureMapperYZ_class[];
extern E3dTextureMapperClass  E3dTextureMapperPerspective_class[];
extern E3dTextureMapperClass  E3dTextureMapperUV_class[];
extern E3dTextureMapperClass  E3dTextureMapperLayout_class[];
extern E3dTextureMapperClass  E3dTextureMapperCylindrical_class[];
extern E3dTextureMapperClass  E3dTextureMapperSpherical_class[];
extern E3dTextureMapperClass  E3dTextureMapperAngleMap_class[];
extern E3dTextureMapperClass  E3dTextureMapperMirrorSphere_class[];
extern E3dTextureMapperClass  E3dTextureMapperFishEye_class[];
extern E3dTextureMapperClass  E3dTextureMapperEnvMap_class[];

extern E3dTextureMapperClass  E3dTextureMapperCubeMap_class[];
extern E3dTextureMapperClass  E3dTextureMapperCubeMapReflection_class[];
extern E3dTextureMapperClass  E3dTextureMapperCubeMapRefraction_class[];


extern EBool  (*E3d2DTexture_BeingEdited)(const E3d2DTexture* PTexture);
extern int    (*E3d2DTexture_UpdateGUI)(const E3d2DTexture* PTexture, const EChangeFlags PUpdateFlags, const EBool PUpdateMappingTypeOptionMenu);


#if !ERELEASE_BUILD
typedef struct
{
	E3dTextureMapperCore;
} E3dTextureMapperPlanarReflection;

extern E3dTextureMapperClass  E3dTextureMapperPlanarReflection_class[];
#endif

extern const char         E3dTextureMapperPlanarReflection_classname[];


extern void               E3dTextureMapper_Init(E3dTextureMapper* PTextureMapper);
extern E3dTextureMapper*  E3dTextureMapper_New(E3dTextureMapperClass* PClass);

extern void               E3dTextureMapperPlanar_GetMatrix(const E3dTextureMapperPlanar* PMapper, const E3d2DTexture* PTexture, E3dMatrix PMatrix);

extern E3dTextureMapper*  E3dTextureMapper_Convert(E3dTextureMapper* PMapper, E3dTextureMapperClass* PNewClass);

extern void               E3dTextureMappers_UpdateWorldToMapperMatrix(E3dTextureMapperPtrList* PMappers, const E3dCoordinate* PLocalToWorldMatrix);

extern E3dTextureMapperClass*  E3dTextureMapper_ClassRegister(E3dTextureMapperClass* PClassTemplate);
extern void                    E3dTextureMapper_ClassDeactivate(E3dTextureMapperClass* PClass);
extern void                    E3dTextureMapper_ClassesFree(void);

extern E3dTextureMapperClass*  E3dTextureMapper_ClassByName(const char* PClassName);
extern EBool                   E3d2DTexture_IsEmissive(E3d2DTexture* PTexture);


//----------------------------------------
// E3D/Sampler.c
//----------------------------------------
extern void  _E3d_TextureSample2D_bilinearSimple(const E3d2DTexture* PTexture, const E3dSTWithDiff* PST,  const char PTilingModeS, const char PTilingModeT, float* PResult);

extern void  E3d2DTexture_SetSampler(E3d2DTexture* PTexture, const EBool PMipMapsEnabled, const EBool PRendererHasAnisotropicFiltering);

//----------------------------------------
// GL/Texture.c
//----------------------------------------

#ifndef EpGLTexture_DEF
#define EpGLTexture_DEF
typedef struct EpGLTexture_S  EpGLTexture;
#endif

extern void           E3dTexture_GLSetFiltering(E3d2DTexture* PTexture, EpGLTexture* PGLTexture, const GLenum LGLTarget, const E3dTextureMapper* PMapper);
extern EpGLTexture*   E3dTexture_GLSetup(E3d2DTexture* P2DTexture, const E3dTextureMapper* PMapper, E3dScene* PScene);
extern void           E3dTexture_GLSetImage(E3d2DTexture* PTexture, const E3dTextureMapper* PMapper);

extern void           E3dTexture_GLFilteringInvalidate(E3d2DTexture* PTexture);
extern int            E3dTextureMapper_GetGLTarget(const E3dTextureMapper* PMapper);

extern void           E3dGL_BindTextureToUnit(const GLuint PGLTextureEngine, const GLuint PGLTextureID);

extern void           E3dTexture_GLImagesFree(E3d2DTexture* PTexture);
extern void           E3d2DTexture_FreeGLTextures(E3d2DTexture* PTexture);

// Possible responses from _SceneTextureGetGLImage()
//
enum
{
	E3dGLIMG_SUCCESS = 0,
	E3dGLIMG_ERROR
};

extern EBool          E3d2DTexture_IsAnimated(E3d2DTexture* PTexture);

extern EObjectClass   EpGLTexture_class[];


#if defined (__cplusplus) && (!(defined (__CPLUSLIBS)))
}	// Close scope of 'extern "C"' declaration
#endif

#endif	// _E3D_Texture_h
