From c46b0d0cfa9202092b5827792b248b2907afcf03 Mon Sep 17 00:00:00 2001 From: Fillip Date: Wed, 7 Aug 2024 02:37:51 -0700 Subject: [PATCH] Rename --- .idea/.gitignore | 8 + .idea/ComfyUI_Fill-Nodes.iml | 12 + .idea/inspectionProfiles/Project_Default.xml | 43 ++ .../inspectionProfiles/profiles_settings.xml | 6 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + __init__.py | 52 +-- ...radientImageGenerator.py => FL_GradGen.py} | 0 nodes/FL_KsamplerPlus.py | 40 +- ...l_sdultimate_slices.py => FL_SD_Slices.py} | 0 web/nodes/FL_KsamplerPlus.js | 416 +++++++++++++++--- 11 files changed, 490 insertions(+), 101 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/ComfyUI_Fill-Nodes.iml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml rename nodes/{GradientImageGenerator.py => FL_GradGen.py} (100%) rename nodes/{fl_sdultimate_slices.py => FL_SD_Slices.py} (100%) diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/ComfyUI_Fill-Nodes.iml b/.idea/ComfyUI_Fill-Nodes.iml new file mode 100644 index 0000000..8b8c395 --- /dev/null +++ b/.idea/ComfyUI_Fill-Nodes.iml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..86d07c1 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,43 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..5801c97 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/__init__.py b/__init__.py index 230ab8c..643a201 100644 --- a/__init__.py +++ b/__init__.py @@ -1,41 +1,41 @@ -from .nodes.fl_image_randomizer import FL_ImageRandomizer +from .nodes.FL_Image_Randomizer import FL_ImageRandomizer from .nodes.FL_Image_Caption_Saver import FL_ImageCaptionSaver -from .nodes.fl_image_dimension_display import FL_ImageDimensionDisplay +from .nodes.FL_Image_Dimension_Display import FL_ImageDimensionDisplay from .nodes.FL_Code_Node import FL_CodeNode -from .nodes.fl_image_pixelator import FL_ImagePixelator +from .nodes.FL_Image_Pixelator import FL_ImagePixelator from .nodes.FL_DirectoryCrawl import FL_DirectoryCrawl from .nodes.FL_Ascii import FL_Ascii from .nodes.FL_Glitch import FL_Glitch -from .nodes.fl_ripple import FL_Ripple -from .nodes.fl_pixelsort import FL_PixelSort +from .nodes.FL_Ripple import FL_Ripple +from .nodes.FL_PixelSort import FL_PixelSort from .nodes.FL_HexagonalPattern import FL_HexagonalPattern -from .nodes.fl_nftgenerator import FL_NFTGenerator +from .nodes.FL_NFTGenerator import FL_NFTGenerator from .nodes.FL_HalfTone import FL_HalftonePattern -from .nodes.fl_randomrange import FL_RandomNumber -from .nodes.fl_promptselector import FL_PromptSelector -from .nodes.fl_shader import FL_Shadertoy -from .nodes.fl_pixelshader import FL_PixelArtShader -from .nodes.fl_infinitezoom import FL_InfiniteZoom -from .nodes.fl_paperdrawn import FL_PaperDrawn -from .nodes.fl_imagenotes import FL_ImageNotes -from .nodes.fl_imagecollage import FL_ImageCollage -from .nodes.fl_ksamplersettings import FL_KsamplerSettings -from .nodes.fl_retroeffect import FL_RetroEffect -from .nodes.fl_td_ksampler import FL_TD_KSampler -from .nodes.fl_inpaintcrop import FL_InpaintCrop -from .nodes.fl_inpaintcrop import FL_Inpaint_Stitch -from .nodes.fl_sdultimate_slices import FL_SDUltimate_Slices +from .nodes.FL_RandomRange import FL_RandomNumber +from .nodes.FL_PromptSelector import FL_PromptSelector +from .nodes.FL_Shader import FL_Shadertoy +from .nodes.FL_PixelShader import FL_PixelArtShader +from .nodes.FL_InfiniteZoom import FL_InfiniteZoom +from .nodes.FL_PaperDrawn import FL_PaperDrawn +from .nodes.FL_ImageNotes import FL_ImageNotes +from .nodes.FL_ImageCollage import FL_ImageCollage +from .nodes.FL_KsamplerSettings import FL_KsamplerSettings +from .nodes.FL_RetroEffect import FL_RetroEffect +from .nodes.FL_TD_Ksampler import FL_TD_KSampler +from .nodes.FL_InpaintCrop import FL_InpaintCrop +from .nodes.FL_InpaintCrop import FL_Inpaint_Stitch +from .nodes.FL_SD_Slices import FL_SDUltimate_Slices from .nodes.FL_BatchAligned import FL_BatchAlign -from .nodes.fl_videocropnstitch import FL_VideoCropMask -from .nodes.fl_videocropnstitch import FL_VideoRecompose -from .nodes.fl_separatemasks import FL_SeparateMaskComponents -from .nodes.fl_pasteoncanvas import FL_PasteOnCanvas +from .nodes.FL_VideoCropNStitch import FL_VideoCropMask +from .nodes.FL_VideoCropNStitch import FL_VideoRecompose +from .nodes.FL_SeparateMasks import FL_SeparateMaskComponents +from .nodes.FL_PasteOnCanvas import FL_PasteOnCanvas from .nodes.FL_BulletHellGame import FL_BulletHellGame -from .nodes.fl_tetrisgame import FL_TetrisGame +from .nodes.FL_TetrisGame import FL_TetrisGame from .nodes.FL_Dither import FL_Dither from .nodes.FL_SystemCheck import FL_SystemCheck from .nodes.FL_ColorPicker import FL_ColorPicker -from .nodes.GradientImageGenerator import GradientImageGenerator +from .nodes.FL_GradGen import GradientImageGenerator from .nodes.FL_MirrorAndAppendCaptions import FL_MirrorAndAppendCaptions from .nodes.FL_ImageCaptionLayout import FL_ImageCaptionLayout from .nodes.FL_HFHubModelUploader import FL_HFHubModelUploader diff --git a/nodes/GradientImageGenerator.py b/nodes/FL_GradGen.py similarity index 100% rename from nodes/GradientImageGenerator.py rename to nodes/FL_GradGen.py diff --git a/nodes/FL_KsamplerPlus.py b/nodes/FL_KsamplerPlus.py index 7415c66..488ead2 100644 --- a/nodes/FL_KsamplerPlus.py +++ b/nodes/FL_KsamplerPlus.py @@ -7,6 +7,7 @@ import logging import numpy as np from PIL import Image import torch.nn.functional as F +import latent_preview class FL_KsamplerPlus: @@ -33,7 +34,6 @@ class FL_KsamplerPlus: }, "optional": { "latent_image": ("LATENT",), - "vae": ("VAE",), "image": ("IMAGE",), } } @@ -145,24 +145,28 @@ class FL_KsamplerPlus: return section, y_start, y_end, x_start, x_end, cropped_positive, cropped_negative total_slices = x_slices * y_slices - all_slices = [(y, x) for y in range(y_slices) for x in range(x_slices)] - for i in range(0, total_slices, batch_size): - batch_slices = all_slices[i:min(i + batch_size, total_slices)] + batch_slices = [(y, x) for y in range(y_slices) for x in range(x_slices)][ + i:min(i + batch_size, total_slices)] batch_sections = [process_slice(y, x) for y, x in batch_slices] batch_latents = torch.cat([section for section, _, _, _, _, _, _ in batch_sections], dim=0) - batch_positives = sum([pos for _, _, _, _, _, pos, _ in batch_sections], []) - batch_negatives = sum([neg for _, _, _, _, _, _, neg in batch_sections], []) - processed_batch = common_ksampler(model, seed + i, steps, cfg, sampler_name, scheduler, - batch_positives, batch_negatives, - {"samples": batch_latents}, denoise=denoise)[0] + if use_sliced_conditioning: + batch_positive = [cond for _, _, _, _, _, cond, _ in batch_sections] + batch_negative = [cond for _, _, _, _, _, _, cond in batch_sections] + else: + batch_positive = [positive] * len(batch_sections) + batch_negative = [negative] * len(batch_sections) - processed_sections = torch.split(processed_batch["samples"], b, dim=0) + # Process each slice in the batch individually + for j, (slice_latent, slice_positive, slice_negative) in enumerate( + zip(torch.split(batch_latents, 1), batch_positive, batch_negative)): + processed_slice = common_ksampler(model, seed + i + j, steps, cfg, sampler_name, scheduler, + slice_positive, slice_negative, + {"samples": slice_latent}, denoise=denoise)[0] - for (_, y_start, y_end, x_start, x_end, _, _), processed_section in zip(batch_sections, - processed_sections): + _, y_start, y_end, x_start, x_end, _, _ = batch_sections[j] is_top = y_start == 0 is_left = x_start == 0 is_bottom = y_end == h @@ -170,14 +174,14 @@ class FL_KsamplerPlus: blend_mask = create_blend_mask(y_end - y_start, x_end - x_start, overlap_height, overlap_width, is_top, is_left, is_bottom, is_right) - blend_mask = blend_mask.unsqueeze(0).unsqueeze(0).expand_as(processed_section) + blend_mask = blend_mask.unsqueeze(0).unsqueeze(0).expand_as(processed_slice["samples"]) - processed_section = processed_section.to(device=device) + processed_slice = processed_slice["samples"].to(device=device) blend_mask = blend_mask.to(device=device) samples[:, :, y_start:y_end, x_start:x_end] = ( samples[:, :, y_start:y_end, x_start:x_end] * (1 - blend_mask) + - processed_section * blend_mask + processed_slice * blend_mask ) if device.type == 'cuda': @@ -193,3 +197,9 @@ class FL_KsamplerPlus: except Exception as e: logging.error(f"Error in FL_UltimateUpscale: {str(e)}") raise + + @classmethod + def IS_CHANGED(s, model, positive, negative, x_slices, y_slices, overlap, batch_size, seed, steps, cfg, + sampler_name, scheduler, denoise, input_type, use_sliced_conditioning, latent_image=None, image=None, + vae=None): + return float("NaN") \ No newline at end of file diff --git a/nodes/fl_sdultimate_slices.py b/nodes/FL_SD_Slices.py similarity index 100% rename from nodes/fl_sdultimate_slices.py rename to nodes/FL_SD_Slices.py diff --git a/web/nodes/FL_KsamplerPlus.js b/web/nodes/FL_KsamplerPlus.js index 4f9b2d2..45346f0 100644 --- a/web/nodes/FL_KsamplerPlus.js +++ b/web/nodes/FL_KsamplerPlus.js @@ -8,65 +8,359 @@ app.registerExtension({ ]; if (animatedNodeClasses.includes(node.comfyClass)) { - addDenoisingTextAnimatedDisplay(node); + addModernSlotMachineAnimation(node); } } }); -function addDenoisingTextAnimatedDisplay(node) { - const ANIMATION_WIDTH = 120; - const ANIMATION_HEIGHT = 80; - const PIXEL_SIZE = 1; - const GRID_WIDTH = Math.floor(ANIMATION_WIDTH / PIXEL_SIZE); - const GRID_HEIGHT = Math.floor(ANIMATION_HEIGHT / PIXEL_SIZE); +function addModernSlotMachineAnimation(node) { + // Easily adjustable parameters + const PARAMS = { + // Animation dimensions + ANIMATION_WIDTH: 120, + ANIMATION_HEIGHT: 80, + X_OFFSET: 0, + Y_OFFSET: 100, - let grid = createEmptyGrid(GRID_WIDTH, GRID_HEIGHT); - let animationProgress = 0; - let xOffset = 0; - let yOffset = 90; + // Reel settings + REEL_WIDTH: 30, + REEL_HEIGHT: 30, + REEL_COUNT: 3, + SYMBOL_SIZE: 24, + SPIN_SPEED: 0.2, - function createEmptyGrid(width, height) { - return Array.from({ length: height }, () => - Array.from({ length: width }, () => ({ value: 0, hue: 0 })) - ); - } + // Game logic + WIN_PROBABILITY: 0.25, + SYMBOLS: ['💎', '🔮', '🎭', '🎨', '🎬', '🏆'], - function drawText(ctx) { - ctx.font = 'bold 20px Arial'; + // Button settings + BUTTON_WIDTH: 50, + BUTTON_HEIGHT: 20, + BUTTON_Y_OFFSET: 25, + + // Animation timings + SPIN_DURATION: 1000, + WIN_ANIMATION_SPEED: 0.02, + + // Visual styles + BACKGROUND_COLOR: '#4B0082', + FRAME_COLOR: '#8A2BE2', + REEL_COLOR: '#2E0854', + BUTTON_COLORS: { + NORMAL: ['#8A2BE2', '#6A0DAD'], + SPINNING: ['#6A0DAD', '#4B0082'] + }, + TEXT_COLOR: '#FFFFFF', + + // Particle system + MAX_PARTICLES: 100, + PARTICLE_DECAY_RATE: 0.02 + }; + + let reels = Array(PARAMS.REEL_COUNT).fill().map(() => ({ + symbols: [...PARAMS.SYMBOLS], + position: 0, + spinning: false, + finalPosition: 0, + offset: 0 + })); + + let spinning = false; + let winningSymbol = null; + let winAnimation = null; + let winAnimationProgress = 0; + let particles = []; + + function drawModernSlotMachine(ctx) { + // Background + ctx.fillStyle = PARAMS.BACKGROUND_COLOR; + ctx.fillRect(0, 0, PARAMS.ANIMATION_WIDTH, PARAMS.ANIMATION_HEIGHT); + + // Frame + ctx.strokeStyle = PARAMS.FRAME_COLOR; + ctx.lineWidth = 2; + ctx.strokeRect(2, 2, PARAMS.ANIMATION_WIDTH - 4, PARAMS.ANIMATION_HEIGHT - 4); + + // Reels + const reelStartX = (PARAMS.ANIMATION_WIDTH - (PARAMS.REEL_COUNT * PARAMS.REEL_WIDTH + (PARAMS.REEL_COUNT - 1) * 5)) / 2; + reels.forEach((reel, i) => { + ctx.fillStyle = PARAMS.REEL_COLOR; + ctx.fillRect(reelStartX + i * (PARAMS.REEL_WIDTH + 5), 15, PARAMS.REEL_WIDTH, PARAMS.REEL_HEIGHT); + + ctx.save(); + ctx.beginPath(); + ctx.rect(reelStartX + i * (PARAMS.REEL_WIDTH + 5), 15, PARAMS.REEL_WIDTH, PARAMS.REEL_HEIGHT); + ctx.clip(); + + const symbolIndex = Math.floor(reel.position) % PARAMS.SYMBOLS.length; + const y = 30 - (reel.position % 1) * PARAMS.SYMBOL_SIZE + reel.offset; + ctx.font = `${PARAMS.SYMBOL_SIZE}px Arial`; + ctx.fillStyle = PARAMS.TEXT_COLOR; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(reel.symbols[symbolIndex], reelStartX + PARAMS.REEL_WIDTH / 2 + i * (PARAMS.REEL_WIDTH + 5), y); + + ctx.restore(); + }); + + // Spin button + const buttonGradient = ctx.createLinearGradient(0, PARAMS.ANIMATION_HEIGHT - PARAMS.BUTTON_Y_OFFSET, 0, PARAMS.ANIMATION_HEIGHT - PARAMS.BUTTON_Y_OFFSET + PARAMS.BUTTON_HEIGHT); + const buttonColors = spinning ? PARAMS.BUTTON_COLORS.SPINNING : PARAMS.BUTTON_COLORS.NORMAL; + buttonGradient.addColorStop(0, buttonColors[0]); + buttonGradient.addColorStop(1, buttonColors[1]); + ctx.fillStyle = buttonGradient; + ctx.beginPath(); + ctx.roundRect(PARAMS.ANIMATION_WIDTH / 2 - PARAMS.BUTTON_WIDTH / 2, PARAMS.ANIMATION_HEIGHT - PARAMS.BUTTON_Y_OFFSET, PARAMS.BUTTON_WIDTH, PARAMS.BUTTON_HEIGHT, 5); + ctx.fill(); + ctx.fillStyle = PARAMS.TEXT_COLOR; + ctx.font = 'bold 12px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; - ctx.fillStyle = 'white'; - ctx.fillText('CUT', ANIMATION_WIDTH / 2, ANIMATION_HEIGHT / 2 - 15); - ctx.fillText('THE NOISE', ANIMATION_WIDTH / 2, ANIMATION_HEIGHT / 2 + 15); - } + ctx.fillText('SPIN', PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT - PARAMS.BUTTON_Y_OFFSET + PARAMS.BUTTON_HEIGHT / 2); - function updateGrid() { - animationProgress += 0.005; // Slightly faster animation - if (animationProgress > 1) { - animationProgress = 0; - grid = createEmptyGrid(GRID_WIDTH, GRID_HEIGHT); + // Win animation + if (winAnimation) { + winAnimation(ctx, winAnimationProgress); } - const tempCanvas = document.createElement('canvas'); - tempCanvas.width = ANIMATION_WIDTH; - tempCanvas.height = ANIMATION_HEIGHT; - const tempCtx = tempCanvas.getContext('2d'); - drawText(tempCtx); + // Draw particles + particles.forEach(p => { + ctx.fillStyle = p.color; + ctx.beginPath(); + ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); + ctx.fill(); + }); + } - const imageData = tempCtx.getImageData(0, 0, ANIMATION_WIDTH, ANIMATION_HEIGHT); + function spin() { + if (spinning) return; + spinning = true; + winningSymbol = null; + winAnimation = null; + winAnimationProgress = 0; + particles = []; - for (let y = 0; y < GRID_HEIGHT; y++) { - for (let x = 0; x < GRID_WIDTH; x++) { - const i = (y * ANIMATION_WIDTH + x) * 4; - if (imageData.data[i] > 0) { - if (grid[y][x].value === 0) { - grid[y][x].hue = Math.random() * 360; // Assign a random hue - } - if (Math.random() < animationProgress * 0.2) { - grid[y][x].value = Math.min(grid[y][x].value + 0.2, 1); - } + reels.forEach(reel => { + reel.spinning = true; + reel.spinDuration = 50 + Math.random() * 50; + reel.offset = Math.random() * PARAMS.SYMBOL_SIZE; + }); + + const isWin = Math.random() < PARAMS.WIN_PROBABILITY; + if (isWin) { + const winSymbolIndex = Math.floor(Math.random() * PARAMS.SYMBOLS.length); + reels.forEach(reel => reel.finalPosition = winSymbolIndex); + winningSymbol = PARAMS.SYMBOLS[winSymbolIndex]; + } else { + reels.forEach(reel => reel.finalPosition = Math.floor(Math.random() * PARAMS.SYMBOLS.length)); + } + + setTimeout(() => { + reels.forEach(reel => { + reel.spinning = false; + reel.position = reel.finalPosition; + reel.offset = 0; + }); + spinning = false; + if (isWin) triggerWin(); + }, PARAMS.SPIN_DURATION); + } + + function triggerWin() { + switch (winningSymbol) { + case '💎': winAnimation = diamondWinAnimation; break; + case '🔮': winAnimation = crystalBallWinAnimation; break; + case '🎭': winAnimation = masksWinAnimation; break; + case '🎨': winAnimation = paletteWinAnimation; break; + case '🎬': winAnimation = clapperboardWinAnimation; break; + case '🏆': winAnimation = trophyWinAnimation; break; + } + } + + function diamondWinAnimation(ctx, progress) { + const size = 40 * Math.sin(progress * Math.PI); + ctx.font = `${size}px Arial`; + ctx.fillStyle = `rgba(255, 255, 255, ${1 - progress})`; + ctx.fillText('💎', PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT / 2); + + // Sparkling effect + if (Math.random() < 0.3) { + particles.push({ + x: Math.random() * PARAMS.ANIMATION_WIDTH, + y: Math.random() * PARAMS.ANIMATION_HEIGHT, + size: Math.random() * 3, + color: `hsl(${Math.random() * 360}, 100%, 50%)`, + life: 1 + }); + } + } + + function crystalBallWinAnimation(ctx, progress) { + ctx.fillStyle = `rgba(128, 0, 128, ${0.5 - progress * 0.5})`; + ctx.beginPath(); + ctx.arc(PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT / 2, 30 * progress, 0, Math.PI * 2); + ctx.fill(); + + // Swirling mist effect + for (let i = 0; i < 5; i++) { + const angle = progress * Math.PI * 2 + i * Math.PI / 2.5; + const x = PARAMS.ANIMATION_WIDTH / 2 + Math.cos(angle) * 20 * progress; + const y = PARAMS.ANIMATION_HEIGHT / 2 + Math.sin(angle) * 20 * progress; + ctx.font = '12px Arial'; + ctx.fillStyle = `rgba(255, 255, 255, ${1 - progress})`; + ctx.fillText('✨', x, y); + } + + ctx.font = '24px Arial'; + ctx.fillStyle = 'white'; + ctx.fillText('🔮', PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT / 2); + } + + function masksWinAnimation(ctx, progress) { + const angle = progress * Math.PI * 4; + ctx.save(); + ctx.translate(PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT / 2); + + // Comedy mask + ctx.rotate(angle); + ctx.font = '24px Arial'; + ctx.fillStyle = `rgba(255, 255, 0, ${1 - progress})`; + ctx.fillText('😂', 0, -20); + + // Tragedy mask + ctx.rotate(Math.PI); + ctx.fillStyle = `rgba(0, 0, 255, ${1 - progress})`; + ctx.fillText('😭', 0, -20); + + ctx.restore(); + + // Confetti effect + if (Math.random() < 0.2) { + particles.push({ + x: Math.random() * PARAMS.ANIMATION_WIDTH, + y: -5, + size: Math.random() * 5 + 2, + color: Math.random() < 0.5 ? 'yellow' : 'blue', + life: 1, + vy: Math.random() * 2 + 1 + }); + } + } + + function paletteWinAnimation(ctx, progress) { + const colors = ['red', 'blue', 'green', 'yellow', 'purple']; + colors.forEach((color, i) => { + const angle = (i / colors.length + progress) * Math.PI * 4; + const x = PARAMS.ANIMATION_WIDTH / 2 + Math.cos(angle) * 30 * Math.sin(progress * Math.PI); + const y = PARAMS.ANIMATION_HEIGHT / 2 + Math.sin(angle) * 30 * Math.sin(progress * Math.PI); + ctx.fillStyle = color; + ctx.beginPath(); + ctx.arc(x, y, 5, 0, Math.PI * 2); + ctx.fill(); + + // Paint splatter effect + if (Math.random() < 0.1) { + particles.push({ + x: x, + y: y, + size: Math.random() * 3 + 1, + color: color, + life: 1, + vx: (Math.random() - 0.5) * 2, + vy: (Math.random() - 0.5) * 2 + }); + } + }); + + ctx.font = '24px Arial'; + ctx.fillStyle = 'white'; + ctx.fillText('🎨', PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT / 2); + } + + function clapperboardWinAnimation(ctx, progress) { + ctx.save(); + ctx.translate(PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT / 2); + + // Clapping effect + const clapAngle = Math.sin(progress * Math.PI * 8) * 0.3; + ctx.rotate(clapAngle); + ctx.font = '24px Arial'; + ctx.fillStyle = 'white'; + ctx.fillText('🎬', 0, 0); + + // Starburst effect + for (let i = 0; i < 8; i++) { + const angle = i * Math.PI / 4 + progress * Math.PI * 2; + const length = 20 + Math.sin(progress * Math.PI) * 10; + ctx.strokeStyle = `rgba(255, 255, 0, ${1 - progress})`; + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(Math.cos(angle) * length, Math.sin(angle) * length); + ctx.stroke(); + } + + ctx.restore(); + } + + function trophyWinAnimation(ctx, progress) { + const y = PARAMS.ANIMATION_HEIGHT / 2 + Math.sin(progress * Math.PI * 2) * 10; + ctx.font = '24px Arial'; + ctx.fillStyle = `rgba(255, 215, 0, ${1 - progress})`; + ctx.fillText('🏆', PARAMS.ANIMATION_WIDTH / 2, y); + + // Fireworks effect + if (Math.random() < 0.2) { + const x = Math.random() * PARAMS.ANIMATION_WIDTH; + const y = PARAMS.ANIMATION_HEIGHT; + for (let i = 0; i < 20; i++) { + const angle = Math.random() * Math.PI * 2; + const speed = Math.random() * 2 + 1; + particles.push({ + x: x, + y: y, + size: Math.random() * 2 + 1, + color: `hsl(${Math.random() * 360}, 100%, 50%)`, + life: 1, + vx: Math.cos(angle) * speed, + vy: Math.sin(angle) * speed - 2 + }); + } + } + + ctx.font = '16px Arial'; + ctx.fillStyle = `rgba(255, 255, 255, ${progress})`; + ctx.fillText('JACKPOT!', PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT / 2 + 30); + } + + function updateAnimation() { + reels.forEach(reel => { + if (reel.spinning) { + reel.position += PARAMS.SPIN_SPEED; + if (reel.offset > 0) { + reel.offset -= 1; } } + }); + + if (winAnimation) { + winAnimationProgress += PARAMS.WIN_ANIMATION_SPEED; + if (winAnimationProgress >= 1) { + winAnimation = null; + winAnimationProgress = 0; + } + } + + // Update particles + particles = particles.filter(p => { + p.x += p.vx || 0; + p.y += p.vy || 0; + p.life -= PARAMS.PARTICLE_DECAY_RATE; + return p.life > 0; + }); + + // Limit the number of particles + if (particles.length > PARAMS.MAX_PARTICLES) { + particles.splice(0, particles.length - PARAMS.MAX_PARTICLES); } } @@ -75,32 +369,34 @@ function addDenoisingTextAnimatedDisplay(node) { ctx.save(); const nodeWidth = this.size[0]; - const baseXOffset = (nodeWidth - ANIMATION_WIDTH) / 2; - ctx.translate(baseXOffset + xOffset, -ANIMATION_HEIGHT + yOffset); + const baseXOffset = (nodeWidth - PARAMS.ANIMATION_WIDTH) / 2; + ctx.translate(baseXOffset + PARAMS.X_OFFSET, -PARAMS.ANIMATION_HEIGHT + PARAMS.Y_OFFSET); - ctx.clearRect(0, 0, ANIMATION_WIDTH, ANIMATION_HEIGHT); - - // Draw appearing pixels with color and pulse effect - for (let y = 0; y < GRID_HEIGHT; y++) { - for (let x = 0; x < GRID_WIDTH; x++) { - const { value, hue } = grid[y][x]; - if (value > 0) { - const pulseEffect = 0.7 + 0.3 * Math.sin(animationProgress * 10 + x * 0.1 + y * 0.1); - const alpha = value * pulseEffect; - ctx.fillStyle = `hsla(${hue}, 100%, 50%, ${alpha})`; - ctx.fillRect(x * PIXEL_SIZE, y * PIXEL_SIZE, PIXEL_SIZE, PIXEL_SIZE); - } - } - } + drawModernSlotMachine(ctx); ctx.restore(); - updateGrid(); + updateAnimation(); this.setDirtyCanvas(true); requestAnimationFrame(() => this.setDirtyCanvas(true)); } }; + node.onMouseDown = function(event) { + const nodeWidth = this.size[0]; + const baseXOffset = (nodeWidth - PARAMS.ANIMATION_WIDTH) / 2; + const localX = event.canvasX - this.pos[0] - baseXOffset - PARAMS.X_OFFSET; + const localY = event.canvasY - this.pos[1] + PARAMS.ANIMATION_HEIGHT - PARAMS.Y_OFFSET; + + if (localX >= PARAMS.ANIMATION_WIDTH / 2 - PARAMS.BUTTON_WIDTH / 2 && + localX <= PARAMS.ANIMATION_WIDTH / 2 + PARAMS.BUTTON_WIDTH / 2 && + localY >= PARAMS.ANIMATION_HEIGHT - PARAMS.BUTTON_Y_OFFSET && + localY <= PARAMS.ANIMATION_HEIGHT - PARAMS.BUTTON_Y_OFFSET + PARAMS.BUTTON_HEIGHT) { + spin(); + return true; + } + }; + node.setDirtyCanvas(true); } \ No newline at end of file