Updates to the particle system

This commit is contained in:
AlacrisDevs
2026-02-16 20:57:29 +02:00
parent 3f5b8be36e
commit 57f07e31f6
3 changed files with 91 additions and 13 deletions

View File

@@ -31,6 +31,8 @@
glowIntensity: 0.6, glowIntensity: 0.6,
logoSpin: false, logoSpin: false,
logoSpinSpeed: 5, logoSpinSpeed: 5,
particleImage: "",
particleSize: 1.0,
titlePosition: "top", titlePosition: "top",
}; };
@@ -71,6 +73,8 @@
let glowIntensity = saved.glowIntensity; let glowIntensity = saved.glowIntensity;
let logoSpin = saved.logoSpin; let logoSpin = saved.logoSpin;
let logoSpinSpeed = saved.logoSpinSpeed; let logoSpinSpeed = saved.logoSpinSpeed;
let particleImage = saved.particleImage;
let particleSize = saved.particleSize;
let titlePosition = saved.titlePosition; let titlePosition = saved.titlePosition;
let visualizerComponent; let visualizerComponent;
let toggleHidden = false; let toggleHidden = false;
@@ -126,6 +130,8 @@
glowIntensity, glowIntensity,
logoSpin, logoSpin,
logoSpinSpeed, logoSpinSpeed,
particleImage,
particleSize,
titlePosition, titlePosition,
}; };
try { try {
@@ -183,6 +189,8 @@
glowIntensity = defaults.glowIntensity; glowIntensity = defaults.glowIntensity;
logoSpin = defaults.logoSpin; logoSpin = defaults.logoSpin;
logoSpinSpeed = defaults.logoSpinSpeed; logoSpinSpeed = defaults.logoSpinSpeed;
particleImage = defaults.particleImage;
particleSize = defaults.particleSize;
titlePosition = defaults.titlePosition; titlePosition = defaults.titlePosition;
} }
@@ -311,6 +319,8 @@
{glowIntensity} {glowIntensity}
{logoSpin} {logoSpin}
{logoSpinSpeed} {logoSpinSpeed}
{particleImage}
{particleSize}
bind:isListening bind:isListening
bind:fileCurrentTime bind:fileCurrentTime
bind:fileDuration bind:fileDuration
@@ -381,6 +391,8 @@
bind:glowIntensity bind:glowIntensity
bind:logoSpin bind:logoSpin
bind:logoSpinSpeed bind:logoSpinSpeed
bind:particleImage
bind:particleSize
bind:titlePosition bind:titlePosition
{isListening} {isListening}
{colorPresets} {colorPresets}

View File

@@ -19,6 +19,8 @@
export let glowIntensity = 0.6; export let glowIntensity = 0.6;
export let logoSpin = false; export let logoSpin = false;
export let logoSpinSpeed = 5; export let logoSpinSpeed = 5;
export let particleImage = "";
export let particleSize = 1.0;
const DEFAULT_SIZE = 250; const DEFAULT_SIZE = 250;
const LINE_LIFT = 8; const LINE_LIFT = 8;
@@ -55,6 +57,16 @@
let visualizerWrapper; let visualizerWrapper;
let blobPhase = 0; let blobPhase = 0;
// Particle image cache
let pImg = null;
$: if (particleImage) {
const img = new Image();
img.src = particleImage;
pImg = img;
} else {
pImg = null;
}
// Particle system // Particle system
let particles = []; let particles = [];
@@ -100,7 +112,7 @@
vy: -(1.5 + Math.random() * 4 * loudness), vy: -(1.5 + Math.random() * 4 * loudness),
life: 1.0, life: 1.0,
decay: 0.008 + Math.random() * 0.02, decay: 0.008 + Math.random() * 0.02,
radius: 2 + Math.random() * 5, radius: (2 + Math.random() * 5) * particleSize,
}; };
break; break;
} }
@@ -124,7 +136,7 @@
vy: -(0.8 + Math.random() * 3 * loudness), vy: -(0.8 + Math.random() * 3 * loudness),
life: 1.0, life: 1.0,
decay: 0.006 + Math.random() * 0.012, decay: 0.006 + Math.random() * 0.012,
radius: 2 + Math.random() * 4, radius: (2 + Math.random() * 4) * particleSize,
}; };
break; break;
} }
@@ -147,7 +159,7 @@
vy: Math.sin(tangent) * speed + Math.sin(angle) * speed * 0.3, vy: Math.sin(tangent) * speed + Math.sin(angle) * speed * 0.3,
life: 1.0, life: 1.0,
decay: 0.006 + Math.random() * 0.015, decay: 0.006 + Math.random() * 0.015,
radius: 2 + Math.random() * 6, radius: (2 + Math.random() * 6) * particleSize,
}; };
break; break;
} }
@@ -164,7 +176,7 @@
vy: Math.sin(angle) * speed, vy: Math.sin(angle) * speed,
life: 1.0, life: 1.0,
decay: 0.005 + Math.random() * 0.015, decay: 0.005 + Math.random() * 0.015,
radius: 3 + Math.random() * 7, radius: (3 + Math.random() * 7) * particleSize,
}; };
break; break;
} }
@@ -193,15 +205,24 @@
particles.pop(); particles.pop();
continue; continue;
} }
const alpha = Math.floor(p.life * 200) if (pImg && pImg.complete) {
.toString(16) const sz = p.radius * p.life * 3;
.padStart(2, "0"); ctx.globalAlpha = p.life;
const color = ctx.drawImage(pImg, p.x - sz / 2, p.y - sz / 2, sz, sz);
useSecondary && Math.random() > 0.5 ? colors.secondary : colors.primary; ctx.globalAlpha = 1;
ctx.beginPath(); } else {
ctx.arc(p.x, p.y, p.radius * p.life, 0, Math.PI * 2); const alpha = Math.floor(p.life * 200)
ctx.fillStyle = color + alpha; .toString(16)
ctx.fill(); .padStart(2, "0");
const color =
useSecondary && Math.random() > 0.5
? colors.secondary
: colors.primary;
ctx.beginPath();
ctx.arc(p.x, p.y, p.radius * p.life, 0, Math.PI * 2);
ctx.fillStyle = color + alpha;
ctx.fill();
}
} }
} }

View File

@@ -56,6 +56,8 @@
export let glowIntensity = 0.6; export let glowIntensity = 0.6;
export let logoSpin = false; export let logoSpin = false;
export let logoSpinSpeed = 5; export let logoSpinSpeed = 5;
export let particleImage = "";
export let particleSize = 1.0;
export let titlePosition = "top"; export let titlePosition = "top";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
@@ -89,6 +91,13 @@
if (file) readFileAsDataUrl(file, (url) => (logoUrl = url)); if (file) readFileAsDataUrl(file, (url) => (logoUrl = url));
} }
let particleImgInput;
function handleParticleImgUpload(e) {
const file = e.target.files[0];
if (file) readFileAsDataUrl(file, (url) => (particleImage = url));
}
function handleBgUpload(e) { function handleBgUpload(e) {
const file = e.target.files[0]; const file = e.target.files[0];
if (file) readFileAsDataUrl(file, (url) => (bgImage = url)); if (file) readFileAsDataUrl(file, (url) => (bgImage = url));
@@ -553,6 +562,42 @@
bind:value={particleDensity} bind:value={particleDensity}
/> />
</div> </div>
<div class="slider-row">
<label for="slider-psize"
>Size <span class="slider-val">{particleSize.toFixed(1)}</span></label
>
<input
id="slider-psize"
type="range"
min="0.3"
max="5.0"
step="0.1"
bind:value={particleSize}
/>
</div>
<div class="inline-row" style="margin-top: 0.5rem; gap: 0.4rem">
<input
type="file"
accept="image/*"
on:change={handleParticleImgUpload}
bind:this={particleImgInput}
id="particle-img-upload"
style="display: none"
/>
<label for="particle-img-upload" class="upload-btn"
>Particle Image</label
>
{#if particleImage}
<img
src={particleImage}
alt="particle"
style="width: 22px; height: 22px; object-fit: cover; border-radius: 4px"
/>
<button class="clear-btn" on:click={() => (particleImage = "")}
>Clear</button
>
{/if}
</div>
{/if} {/if}
<div class="slider-row"> <div class="slider-row">
<label for="slider-shake" <label for="slider-shake"