// Enhanced Occlusion Layer Module for Foundry VTT

export function registerOcclusionLayerConfig2ForUpload() {
	// Global Hook Registration
	function registerGlobalHooks() {
		const updateTriggers = [ // Update triggers for occlusion layer
			'canvasReady', 
			'canvasPan',
			'canvasTokensRefresh', 
			'updateToken', 
			'controlToken', 
			'refreshToken',
			'preUpdateScene',  // Added to ensure update on scene modifications
			'updateScene'      // Added to ensure update on scene modifications
		];

		updateTriggers.forEach(hookName => {
			Hooks.on(hookName, () => {
				updateOcclusionLayer();
			});
		});
	}

	// Initial layer setup
	Hooks.once('ready', () => {
		if (canvas.ready)
			initializeOcclusionLayer();
	});

	// Initialize on canvas setup
	Hooks.on('canvasInit', () => {
		initializeOcclusionLayer();
	});

	// Reset on scene change
	Hooks.on('changeScene', () => {
		if (occlusionConfig.container) {
			canvas.stage.removeChild(occlusionConfig.container);
			occlusionConfig.container.destroy({ children: true });
			occlusionConfig.container = null;
			occlusionConfig.tokensLayer = null;
			occlusionConfig.initialized = false;
		}
	});

	// Start the module
	registerGlobalHooks();
}











// Persistent occlusion layer configuration
const occlusionConfig = {
	container: null,
	tokensLayer: null,
	initialized: false
};

// Initialize or reset the occlusion layer
function initializeOcclusionLayer() {
	// Remove existing container if it exists
	if (occlusionConfig.container) {
		canvas.stage.removeChild(occlusionConfig.container);
		occlusionConfig.container.destroy({ children: true });
	}

	// Create the main occlusion container
	occlusionConfig.container = new PIXI.Container();
	occlusionConfig.container.name = "OcclusionContainer";
	occlusionConfig.container.eventMode = 'passive';

	// Create a layer for occlusion tokens
	occlusionConfig.tokensLayer = new PIXI.Container();
	occlusionConfig.tokensLayer.name = "OcclusionTokens";
	occlusionConfig.tokensLayer.sortableChildren = true;

	// Add the layer to the container
	occlusionConfig.container.addChild(occlusionConfig.tokensLayer);

	// Add to canvas stage
	canvas.stage.addChild(occlusionConfig.container);
	canvas.stage.sortChildren();

	occlusionConfig.initialized = true;
}



// let updateTimeout = null;

// // Comprehensive update mechanism for occlusion layer
// function updateOcclusionLayer() {
// 	// // Cancele qualquer atualização pendente
// 	// if (updateTimeout) {
// 	// 	clearTimeout(updateTimeout);
// 	// }

// 	// Agende uma atualização com um pequeno atraso
// 	// updateTimeout = setTimeout(() => {
// 	// 	if (!canvas.ready) return;
// 	//  //adiciona o código que deve ser debounçado
// 	//}, 50);
		
// 		// Ensure canvas is ready and layer is initialized
// 		if (!canvas.ready) return;

// 		// Reinitialize if not yet set up
// 		if (!occlusionConfig.initialized) {
// 			initializeOcclusionLayer();
// 		}

// 		// Clear existing occlusion tokens
// 		occlusionConfig.tokensLayer.removeChildren();

// 		// Get all tokens and tiles
// 		const tokens = canvas.tokens.placeables;
// 		const tiles = canvas.tiles.placeables;

// 		tokens.forEach(token => {
// 			// Find tiles that intersect with this token
// 			const intersectingTiles = tiles.filter(tile => 
// 				checkTokenTileIntersection(token, tile)
// 			);

// 			// If there are intersecting tiles, create an occlusion sprite
// 			if (intersectingTiles.length > 0) {
// 				const occlusionSprite = createOcclusionSprite(token, intersectingTiles);
// 				if (occlusionSprite) {
// 					occlusionConfig.tokensLayer.addChild(occlusionSprite);
// 				}
// 			}
// 		});
// }







// Token-Tile Intersection Check
// function checkTokenTileIntersection(token, tile) {
// 	// Basic intersection check using bounding boxes
// 	const tokenBounds = token.mesh.getBounds();
// 	const tileBounds = tile.mesh.getBounds();

// 	return (
// 		tokenBounds.x < tileBounds.x + tileBounds.width &&
// 		tokenBounds.x + tokenBounds.width > tileBounds.x &&
// 		tokenBounds.y < tileBounds.y + tileBounds.height &&
// 		tokenBounds.y + tokenBounds.height > tileBounds.y
// 	);
// }

// Use uma chave de string em vez de objeto
const intersectionCache = new Map();

function checkTokenTileIntersection(token, tile) {
		// Crie uma chave única usando IDs ou outras propriedades únicas
		const cacheKey = `${token.id || token.document?.id}-${tile.id || tile.document?.id}`;
    
		// Verifique se já calculamos essa intersecção
		if (intersectionCache.has(cacheKey)) {
				return intersectionCache.get(cacheKey);
		}

		const tokenBounds = token.mesh.getBounds();
		const tileBounds = tile.mesh.getBounds();

		const intersects = (
				tokenBounds.x < tileBounds.x + tileBounds.width &&
				tokenBounds.x + tokenBounds.width > tileBounds.x &&
				tokenBounds.y < tileBounds.y + tileBounds.height &&
				tokenBounds.y + tokenBounds.height > tileBounds.y
		);

		// Armazene o resultado no cache
		intersectionCache.set(cacheKey, intersects);
		return intersects;
}






// Create Occlusion Sprite with Advanced Masking
function createOcclusionSprite(token, intersectingTiles) {
	if (!token.mesh || !token.mesh.texture) return null;

	// Create a sprite from the token's texture
	const sprite = new PIXI.Sprite(token.mesh.texture);
	sprite.position.set(token.mesh.position.x, token.mesh.position.y);
	sprite.anchor.set(token.mesh.anchor.x, token.mesh.anchor.y);
	sprite.angle = token.mesh.angle;
	sprite.scale.set(token.mesh.scale.x, token.mesh.scale.y);

	// Create a mask for the occlusion
	const mask = createOcclusionMask(token, intersectingTiles);
	
	if (mask) {
		sprite.mask = mask;
	}

	// sprite.filters = [colorMatrix, outlineFilter, alphaFilter];
	sprite.filters = [colorMatrix, outlineFilter]; //criados no fim do arquivo

	//sprite.alpha = 0.75;
	sprite.eventMode = 'passive';

	return sprite;
}














// Advanced Occlusion Mask Creation
/*function createOcclusionMask(token, intersectingTiles) {
	const maskGraphics = new PIXI.Graphics();
	maskGraphics.beginFill(0xffffff);

	// Create a temporary canvas once
	const tempCanvas = document.createElement('canvas');
	const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });

	intersectingTiles.forEach(tile => {
		const tokenBounds = token.mesh.getBounds();
		const tileBounds = tile.mesh.getBounds();
				
		// Calculate intersection area
		const x = Math.max(tokenBounds.x, tileBounds.x);
		const y = Math.max(tokenBounds.y, tileBounds.y);
		const width = Math.min(tokenBounds.x + tokenBounds.width, tileBounds.x + tileBounds.width) - x;
		const height = Math.min(tokenBounds.y + tokenBounds.height, tileBounds.y + tileBounds.height) - y;

		// Skip if no intersection
		if (width <= 0 || height <= 0) return;

		// Access tile texture
		const tileTexture = tile.texture?.baseTexture?.resource?.source;
		if (!tileTexture || tileTexture.width <= 0 || tileTexture.height <= 0) return;

		// Set canvas size to intersection size
		tempCanvas.width = Math.ceil(width);
		tempCanvas.height = Math.ceil(height);

		// Clear the canvas
		tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height);

		// Calculate source and destination rectangles
		const sourceX = (x - tileBounds.x) * (tileTexture.width / tileBounds.width);
		const sourceY = (y - tileBounds.y) * (tileTexture.height / tileBounds.height);
		const sourceWidth = width * (tileTexture.width / tileBounds.width);
		const sourceHeight = height * (tileTexture.height / tileBounds.height);

		// Draw the relevant portion of the tile
		tempCtx.drawImage(tileTexture,
			sourceX, sourceY, sourceWidth, sourceHeight,
			0, 0, tempCanvas.width, tempCanvas.height
		);

		// Get image data for the intersection area
		const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
		const data = imageData.data;

		// Use a more efficient approach to detect non-transparent regions
		const chunkSize = 1; // Process 4x4 pixel blocks at once
		const widthInChunks = Math.ceil(tempCanvas.width / chunkSize);
		const heightInChunks = Math.ceil(tempCanvas.height / chunkSize);

		for (let cy = 0; cy < heightInChunks; cy++) {
			for (let cx = 0; cx < widthInChunks; cx++) {
				let hasOpaquePixel = false;

				// Check the chunk for any non-transparent pixels
				chunkLoop: for (let py = 0; py < chunkSize; py++) {
					const y = cy * chunkSize + py;
					if (y >= tempCanvas.height) break;

					for (let px = 0; px < chunkSize; px++) {
						const x = cx * chunkSize + px;
						if (x >= tempCanvas.width) break;

						const i = (y * tempCanvas.width + x) * 4 + 3;
						if (data[i] > 0) {
							hasOpaquePixel = true;
							break chunkLoop;
						}
					}
				}

				// If chunk has any non-transparent pixels, draw it
				if (hasOpaquePixel) {
					const chunkX = x + (cx * chunkSize);
					const chunkY = y + (cy * chunkSize);
					const chunkW = Math.min(chunkSize, tempCanvas.width - cx * chunkSize);
					const chunkH = Math.min(chunkSize, tempCanvas.height - cy * chunkSize);

					maskGraphics.drawRect(chunkX, chunkY, chunkW, chunkH);
				}
			}
		}
	});

	// Clean up
	tempCanvas.remove();

	return maskGraphics;
}*/












/*
function createOcclusionMask(token, intersectingTiles, chunkSize = 2) {
	const maskGraphics = new PIXI.Graphics();
	maskGraphics.beginFill(0xffffff);

	const tempCanvas = document.createElement('canvas');
	const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });

	intersectingTiles.forEach(tile => {
		const tokenBounds = token.mesh.getBounds();
		const tileBounds = tile.mesh.getBounds();
		
		const x = Math.max(tokenBounds.x, tileBounds.x);
		const y = Math.max(tokenBounds.y, tileBounds.y);
		const width = Math.min(tokenBounds.x + tokenBounds.width, tileBounds.x + tileBounds.width) - x;
		const height = Math.min(tokenBounds.y + tokenBounds.height, tileBounds.y + tileBounds.height) - y;

		if (width <= 0 || height <= 0) return;

		const tileTexture = tile.texture?.baseTexture?.resource?.source;
		if (!tileTexture || tileTexture.width <= 0 || tileTexture.height <= 0) return;

		tempCanvas.width = Math.ceil(width);
		tempCanvas.height = Math.ceil(height);

		const sourceX = (x - tileBounds.x) * (tileTexture.width / tileBounds.width);
		const sourceY = (y - tileBounds.y) * (tileTexture.height / tileBounds.height);
		const sourceWidth = width * (tileTexture.width / tileBounds.width);
		const sourceHeight = height * (tileTexture.height / tileBounds.height);

		tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height);

		tempCtx.drawImage(tileTexture,
				sourceX, sourceY, sourceWidth, sourceHeight,
				0, 0, tempCanvas.width, tempCanvas.height
		);

		const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
		const data = imageData.data;

		const width32 = tempCanvas.width;
		const height32 = tempCanvas.height;
		
		const dataU32 = new Uint32Array(imageData.data.buffer);

		for (let cy = 0; cy < height32; cy += chunkSize) {
			for (let cx = 0; cx < width32; cx += chunkSize) {
				let hasOpaquePixel = false;

				chunkCheck: for (let by = 0; by < chunkSize; by++) {
					const y = cy + by;
					if (y >= height32) break;

					for (let bx = 0; bx < chunkSize; bx++) {
						const x = cx + bx;
						if (x >= width32) break;

						const pixelIndex = y * width32 + x;
						const alphaValue = dataU32[pixelIndex] >>> 24;

						if (alphaValue > 0) {
							hasOpaquePixel = true;
							break chunkCheck;
						}
					}
				}

				if (hasOpaquePixel) {
					maskGraphics.drawRect(
						x + cx, 
						y + cy, 
						Math.min(chunkSize, width32 - cx), 
						Math.min(chunkSize, height32 - cy)
					);
				}
			}
		}
	});

	tempCanvas.remove();

	return maskGraphics;
}

// Pode chamar o código de duas maneiras agora, 
// Usando o tamanho de chunk padrão (1):
//     createOcclusionMask(token, intersectingTiles)
// Ou especificando um tamanho de chunk diferente:
//     createOcclusionMask(token, intersectingTiles, 2)

*/

// Cache de máscaras de tokens
const tokenMaskCache = new WeakMap();

function createOcclusionMask(token, intersectingTiles, chunkSize = 1) {
		// Verificar se já temos uma máscara em cache para este token
		const cachedMask = tokenMaskCache.get(token);
		if (cachedMask && !hasTokenMoved(token)) {
				return cachedMask;
		}

		// Usar PIXI.RenderTexture para extração de pixels
		const renderer = canvas.app.renderer;
    
		const maskGraphics = new PIXI.Graphics();
		maskGraphics.beginFill(0xffffff);

		intersectingTiles.forEach(tile => {
				// Limitar o canvas ao tamanho exato do token
				const tokenBounds = token.mesh.getBounds();
				const tileBounds = tile.mesh.getBounds();
        
				// Calcular intersecção precisa
				const x = Math.max(tokenBounds.x, tileBounds.x);
				const y = Math.max(tokenBounds.y, tileBounds.y);
				const width = Math.min(tokenBounds.x + tokenBounds.width, tileBounds.x + tileBounds.width) - x;
				const height = Math.min(tokenBounds.y + tokenBounds.height, tileBounds.y + tileBounds.height) - y;

				if (width <= 0 || height <= 0) return;

				const tileTexture = tile.texture?.baseTexture?.resource?.source;
				if (!tileTexture || tileTexture.width <= 0 || tileTexture.height <= 0) return;

				// Criar RenderTexture do tamanho exato da intersecção
				const renderTexture = PIXI.RenderTexture.create({
						width: Math.ceil(width),
						height: Math.ceil(height)
				});

				// Criar um sprite para o tile na posição correta
				const tileSprite = new PIXI.Sprite(tile.texture);
				tileSprite.position.set(-x + tileBounds.x, -y + tileBounds.y);

				// Renderizar o tile na RenderTexture
				renderer.render(tileSprite, {
						renderTexture: renderTexture,
						clear: true
				});

				// Extrair pixels da RenderTexture
				const pixels = renderer.extract.pixels(renderTexture);

				// Processar pixels
				for (let cy = 0; cy < height; cy += chunkSize) {
						for (let cx = 0; cx < width; cx += chunkSize) {
								let hasOpaquePixel = false;

								chunkCheck: for (let by = 0; by < chunkSize; by++) {
										const y = cy + by;
										if (y >= height) break;

										for (let bx = 0; bx < chunkSize; bx++) {
												const x = cx + bx;
												if (x >= width) break;

												// Calcular índice de pixel
												const pixelIndex = (y * width + x) * 4 + 3;
                        
												// Verificar canal alfa
												if (pixels[pixelIndex] > 0) {
														hasOpaquePixel = true;
														break chunkCheck;
												}
										}
								}

								// Desenhar retângulo se tiver pixel opaco
								if (hasOpaquePixel) {
										maskGraphics.drawRect(
												x + cx, 
												y + cy, 
												Math.min(chunkSize, width - cx), 
												Math.min(chunkSize, height - cy)
										);
								}
						}
				}

				// Limpar RenderTexture
				renderTexture.destroy(true);
		});

		// Armazenar máscara em cache
		tokenMaskCache.set(token, maskGraphics);

		return maskGraphics;
}

// Função para verificar se o token se moveu
function hasTokenMoved(token) {
		const cachedPosition = token._lastMaskPosition;
		const currentPosition = token.mesh.position;

		if (!cachedPosition) {
				// Primeira vez, armazena a posição atual
				token._lastMaskPosition = { x: currentPosition.x, y: currentPosition.y };
				return true;
		}

		// Verificar se houve movimento significativo (ajuste o valor conforme necessário)
		const moved = Math.abs(currentPosition.x - cachedPosition.x) > 0.1 ||
									Math.abs(currentPosition.y - cachedPosition.y) > 0.1;

		if (moved) {
				// Atualiza a posição cached
				token._lastMaskPosition = { x: currentPosition.x, y: currentPosition.y };
		}

		return moved;
}

// Modificar a função de atualização para usar o cache
function updateOcclusionLayer() {
		// Limpar configurações antigas
		occlusionConfig.tokensLayer.removeChildren();

		const tokens = canvas.tokens.placeables;
		const tiles = canvas.tiles.placeables;

		tokens.forEach(token => {
				const intersectingTiles = tiles.filter(tile => 
						checkTokenTileIntersection(token, tile)
				);

				if (intersectingTiles.length > 0) {
						const occlusionSprite = createOcclusionSprite(token, intersectingTiles);
						if (occlusionSprite) {
								occlusionConfig.tokensLayer.addChild(occlusionSprite);
						}
				}
		});
}


















// Definição do isoOutlineFilter
if (typeof PIXI !== 'undefined' && PIXI.filters) {
	const vertexShader =`
		attribute vec2 aVertexPosition;
		attribute vec2 aTextureCoord;
		uniform mat3 projectionMatrix;
		varying vec2 vTextureCoord;

		void main(void) {
			gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
			vTextureCoord = aTextureCoord;
		}
	`;

	const fragmentShader = `
		varying vec2 vTextureCoord;

		uniform sampler2D uSampler;
		uniform float alpha;
		uniform vec2 outlineThickness;
		uniform vec4 outlineColor;
		uniform vec4 filterArea;
		uniform vec4 filterClamp;

		void main(void) {
			vec4 ownColor = texture2D(uSampler, vTextureCoord);
			vec4 curColor;
			float maxAlpha = 0.0;
			vec2 displaced;

			for (float angle = 0.0; angle < 6.28318530718; angle += 0.78539816339) {
				displaced.x = vTextureCoord.x + outlineThickness.x * cos(angle);
				displaced.y = vTextureCoord.y + outlineThickness.y * sin(angle);
				curColor = texture2D(uSampler, displaced);
				maxAlpha = max(maxAlpha, curColor.a);
			}
			float resultAlpha = max(maxAlpha, ownColor.a);
			gl_FragColor = vec4((ownColor.rgb * ownColor.a + outlineColor.rgb * (1.0 - ownColor.a)) * resultAlpha, resultAlpha);
		 }
	`;

	const fragmentShader2 = `
			varying vec2 vTextureCoord;

			uniform sampler2D uSampler;
			uniform float alpha;
			uniform vec2 outlineThickness;
			uniform vec4 outlineColor;
			uniform vec4 filterArea;
			uniform vec4 filterClamp;

			void main(void) {
					vec4 ownColor = texture2D(uSampler, vTextureCoord);
					vec4 curColor;
					float maxAlpha = 0.0;
					vec2 displaced;
        
					// Outline externo
					for (float angle = 0.0; angle < 6.28318530718; angle += 0.78539816339) {
							displaced.x = vTextureCoord.x + outlineThickness.x * cos(angle);
							displaced.y = vTextureCoord.y + outlineThickness.y * sin(angle);
							curColor = texture2D(uSampler, displaced);
							maxAlpha = max(maxAlpha, curColor.a);
					}

					// Outline interno
					float innerAlpha = 0.0;
					for (float angle = 0.0; angle < 6.28318530718; angle += 0.78539816339) {
							displaced.x = vTextureCoord.x - (outlineThickness.x * 0.5) * cos(angle);
							displaced.y = vTextureCoord.y - (outlineThickness.y * 0.5) * sin(angle);
							curColor = texture2D(uSampler, displaced);
							innerAlpha = max(innerAlpha, step(curColor.a, 0.1));  // Detecta transição para transparente
					}
        
					float resultAlpha = max(maxAlpha, ownColor.a);
					float innerEffect = innerAlpha * ownColor.a;  // Aplica apenas em áreas não transparentes

					// Combina os efeitos
					vec3 color = ownColor.rgb;
					if (ownColor.a < 0.1) {  // Pixel transparente
							color = outlineColor.rgb * float(maxAlpha > 0.1);  // Outline externo
					} else if (innerEffect > 0.0) {  // Pixel com outline interno
							color = mix(color, outlineColor.rgb, 0.5);
					}

					gl_FragColor = vec4(color, resultAlpha);
			}
	`;

	const fragmentShader22 = `
			varying vec2 vTextureCoord;

			uniform sampler2D uSampler;
			uniform float alpha;
			uniform vec2 outlineThickness;
			uniform vec4 outlineColor;
			uniform vec4 filterArea;
			uniform vec4 filterClamp;

			void main(void) {
					vec4 ownColor = texture2D(uSampler, vTextureCoord);
					vec4 curColor;
					float maxAlpha = 0.0;
					vec2 displaced;
        
					// Outline externo com mais samples e suavização
					for (float angle = 0.0; angle < 6.28318530718; angle += 0.19634954085) {  // Dividido em 32 samples
							// Múltiplas amostras em diferentes distâncias para cada ângulo
							for (float dist = 0.5; dist <= 1.0; dist += 0.25) {
									displaced.x = vTextureCoord.x + (outlineThickness.x * dist) * cos(angle);
									displaced.y = vTextureCoord.y + (outlineThickness.y * dist) * sin(angle);
									curColor = texture2D(uSampler, displaced);
									maxAlpha = max(maxAlpha, curColor.a * (1.0 - dist * 0.5));  // Peso baseado na distância
							}
					}

					// Outline interno com suavização
					float innerAlpha = 0.0;
					for (float angle = 0.0; angle < 6.28318530718; angle += 0.19634954085) {
							for (float dist = 0.25; dist <= 0.75; dist += 0.25) {
									displaced.x = vTextureCoord.x - (outlineThickness.x * dist) * cos(angle);
									displaced.y = vTextureCoord.y - (outlineThickness.y * dist) * sin(angle);
									curColor = texture2D(uSampler, displaced);
									innerAlpha = max(innerAlpha, step(curColor.a, 0.1) * (1.0 - dist * 0.5));
							}
					}
        
					float resultAlpha = max(maxAlpha, ownColor.a);
					float innerEffect = smoothstep(0.0, 0.5, innerAlpha) * ownColor.a;

					// Suaviza a transição entre cores
					vec3 color = ownColor.rgb;
					if (ownColor.a < 0.1) {
							color = outlineColor.rgb * smoothstep(0.0, 0.3, maxAlpha);
					} else if (innerEffect > 0.0) {
							color = mix(color, outlineColor.rgb, innerEffect * 0.5);
					}

					gl_FragColor = vec4(color, resultAlpha);
			}
	`;

	const fragmentShader31 = `
			varying vec2 vTextureCoord;
			uniform sampler2D uSampler;
			uniform float alpha;
			uniform vec2 outlineThickness;
			uniform vec4 outlineColor;
			uniform vec4 filterArea;
			uniform vec4 filterClamp;

			float gaussian(float x, float sigma) {
					return exp(-(x * x) / (2.0 * sigma * sigma));
			}

			void main(void) {
					vec4 ownColor = texture2D(uSampler, vTextureCoord);
					vec4 curColor;
					float maxAlpha = 0.0;
					float innerAlpha = 0.0;
					vec2 displaced;
        
					// Outline externo com blur gaussiano
					for (float angle = 0.0; angle < 6.28318530718; angle += 0.19634954085) {
							for (float dist = 0.5; dist <= 1.0; dist += 0.25) {
									displaced.x = vTextureCoord.x + (outlineThickness.x * dist) * cos(angle);
									displaced.y = vTextureCoord.y + (outlineThickness.y * dist) * sin(angle);
									curColor = texture2D(uSampler, clamp(displaced, filterClamp.xy, filterClamp.zw));
									float weight = gaussian(dist - 0.75, 0.3);
									maxAlpha = max(maxAlpha, curColor.a * weight);
							}
					}

					// Outline interno com blur gaussiano
					for (float angle = 0.0; angle < 6.28318530718; angle += 0.19634954085) {
							for (float dist = 0.25; dist <= 0.75; dist += 0.25) {
									displaced.x = vTextureCoord.x - (outlineThickness.x * dist) * cos(angle);
									displaced.y = vTextureCoord.y - (outlineThickness.y * dist) * sin(angle);
									curColor = texture2D(uSampler, clamp(displaced, filterClamp.xy, filterClamp.zw));
									float weight = gaussian(dist - 0.5, 0.3);
									innerAlpha = max(innerAlpha, step(curColor.a, 0.1) * weight);
							}
					}
        
					float resultAlpha = max(maxAlpha, ownColor.a);
					float innerEffect = smoothstep(0.0, 0.5, innerAlpha) * ownColor.a;

					vec3 color = ownColor.rgb;
					if (ownColor.a < 0.1) {
							color = outlineColor.rgb * smoothstep(0.0, 0.3, maxAlpha);
					} else if (innerEffect > 0.0) {
							color = mix(color, outlineColor.rgb, innerEffect * 0.5);
					}

					gl_FragColor = vec4(color, resultAlpha * alpha);
			}
	`;
	const fragmentShaderInternOnly = `
			varying vec2 vTextureCoord;
			uniform sampler2D uSampler;
			uniform float alpha;
			uniform vec2 outlineThickness;
			uniform vec4 outlineColor;
			uniform vec4 filterArea;
			uniform vec4 filterClamp;

			float gaussian(float x, float sigma) {
					return exp(-(x * x) / (2.0 * sigma * sigma));
			}

			void main(void) {
					vec4 ownColor = texture2D(uSampler, vTextureCoord);
					vec4 curColor;
					float innerAlpha = 0.0;
					vec2 displaced;
        
					// Outline interno com blur gaussiano
					for (float angle = 0.0; angle < 6.28318530718; angle += 0.19634954085) {
							for (float dist = 0.25; dist <= 0.75; dist += 0.25) {
									displaced.x = vTextureCoord.x - (outlineThickness.x * dist) * cos(angle);
									displaced.y = vTextureCoord.y - (outlineThickness.y * dist) * sin(angle);
									curColor = texture2D(uSampler, clamp(displaced, filterClamp.xy, filterClamp.zw));
									float weight = gaussian(dist - 0.5, 0.3);
									innerAlpha = max(innerAlpha, step(curColor.a, 0.1) * weight);
							}
					}
        
					float innerEffect = smoothstep(0.0, 0.5, innerAlpha) * ownColor.a;

					vec3 color = ownColor.rgb;
					if (innerEffect > 0.0) {
							color = mix(color, outlineColor.rgb, innerEffect * 0.5);
					}

					gl_FragColor = vec4(color, ownColor.a * alpha);
			}
	`;




	// Defina a classe isoOutlineFilter
	class IsoOutlineFilter extends PIXI.Filter {
		constructor(thickness = 1, color = 0x000000, alpha = 1) {
				super(vertexShader, fragmentShader22);

				this.uniforms.outlineColor = new Float32Array(4);
				this.uniforms.outlineThickness = new Float32Array(2);
				this.uniforms.filterArea = new Float32Array(2);
				this.uniforms.alpha = alpha;

				this.color = color;
				this.thickness = thickness;
		}

		get alpha() {
				return this.uniforms.alpha;
		}
	
		set alpha(value) {
				this.uniforms.alpha = value;
		}

		get color() {
				return PIXI.utils.rgb2hex(this.uniforms.outlineColor);
		}
	
		set color(value) {
				PIXI.utils.hex2rgb(value, this.uniforms.outlineColor);
		}

		get thickness() {
				return this.uniforms.outlineThickness[0];
		}
	
		set thickness(value) {
				const filterAreaX = this.uniforms.filterArea[0] || 1;
				const filterAreaY = this.uniforms.filterArea[1] || 1;
			
				this.uniforms.outlineThickness[0] = value / filterAreaX;
				this.uniforms.outlineThickness[1] = value / filterAreaY;
		}
}
/*
	class IsoOutlineFilter extends PIXI.Filter {
		constructor(thickness = 0.5, color = 0x000000, alpha = 0.5) {
			super(vertexShader, fragmentShader22);

			// Inicialize os uniforms
			this.uniforms.outlineColor = new Float32Array(4); // Para armazenar RGBA
			this.uniforms.outlineThickness = new Float32Array(2); // Para armazenar X e Y
			this.uniforms.filterArea = new Float32Array(2); // Para área de filtro
			this.uniforms.alpha = alpha;

			// Configure as propriedades iniciais
			this.color = color;
			this.thickness = thickness;
		}

		get alpha() {
			return this.uniforms.alpha;
		}
		set alpha(value) {
			this.uniforms.alpha = value;
		}

		get color() {
			return PIXI.utils.rgb2hex(this.uniforms.outlineColor);
		}
		set color(value) {
			PIXI.utils.hex2rgb(value, this.uniforms.outlineColor);
		}

		get thickness() {
			return this.uniforms.outlineThickness[0];
		}
		set thickness(value) {
			// Certifique-se de que filterArea tenha valores válidos
			const filterAreaX = this.uniforms.filterArea[0] || 1; // Evite divisão por 0
			const filterAreaY = this.uniforms.filterArea[1] || 1; // Evite divisão por 0
			
			this.uniforms.outlineThickness[0] = value / filterAreaX;
			this.uniforms.outlineThickness[1] = value / filterAreaY;
		}
	}
	*/

	// Adicione o isoOutlineFilter ao namespace PIXI.filters
	PIXI.filters.isoOutlineFilter = IsoOutlineFilter;
} else {
	console.error('PIXI ou PIXI.filters não estão disponíveis.');
}







// Create Deafult Custom Filters 
// Create new outline filter
const outlineFilter = new PIXI.filters.isoOutlineFilter();
outlineFilter.thickness = 0.005;
outlineFilter.color = 0x00ff59;
		
// Create a greyscale/dimming filter
const colorMatrix = new PIXI.ColorMatrixFilter();
colorMatrix.alpha = 1;
colorMatrix.matrix = [
	0.3,  0.0,  0.0,  0.0,  0.0,
	0.0,  0.3,  0.0,  0.0,  0.0,
	0.0,  0.0,  0.3,  0.0,  0.0,
	0.0,  0.0,  0.0,  1.0,  0.0
];

const alphaFilter = new PIXI.AlphaFilter();
alphaFilter.alpha = 0.5;