diff options
| author | 2026-01-16 09:37:20 +0800 | |
|---|---|---|
| committer | 2026-01-16 09:37:20 +0800 | |
| commit | ad36e0ce82770f9b3509ddb1cf96bc3422969806 (patch) | |
| tree | 6df911b964d0d32facf55844c55e61ba3878ae3f /ui/src/lib | |
| parent | 455da9d2b07d05c2bdd1fffa5031a6a329cc5643 (diff) | |
| parent | 963b4b8567ac1bd8b23c41e1bfbd6a99d202d1ed (diff) | |
| download | DropOut-ad36e0ce82770f9b3509ddb1cf96bc3422969806.tar.gz DropOut-ad36e0ce82770f9b3509ddb1cf96bc3422969806.zip | |
Merge pull request #41 from HsiangNianian/main
Diffstat (limited to 'ui/src/lib')
| -rw-r--r-- | ui/src/lib/effects/ConstellationEffect.ts | 129 | ||||
| -rw-r--r-- | ui/src/lib/effects/SaturnEffect.ts | 233 | ||||
| -rw-r--r-- | ui/src/lib/modLoaderApi.ts | 12 |
3 files changed, 187 insertions, 187 deletions
diff --git a/ui/src/lib/effects/ConstellationEffect.ts b/ui/src/lib/effects/ConstellationEffect.ts index 2cc702e..d2db529 100644 --- a/ui/src/lib/effects/ConstellationEffect.ts +++ b/ui/src/lib/effects/ConstellationEffect.ts @@ -1,4 +1,3 @@ - export class ConstellationEffect { private canvas: HTMLCanvasElement; private ctx: CanvasRenderingContext2D; @@ -17,7 +16,7 @@ export class ConstellationEffect { constructor(canvas: HTMLCanvasElement) { this.canvas = canvas; this.ctx = canvas.getContext("2d", { alpha: true })!; - + // Bind methods this.animate = this.animate.bind(this); this.handleMouseMove = this.handleMouseMove.bind(this); @@ -25,7 +24,7 @@ export class ConstellationEffect { // Initial setup this.resize(window.innerWidth, window.innerHeight); this.initParticles(); - + // Mouse interaction window.addEventListener("mousemove", this.handleMouseMove); @@ -44,10 +43,10 @@ export class ConstellationEffect { this.canvas.style.height = `${height}px`; this.ctx.scale(dpr, dpr); - + // Re-initialize if screen size changes significantly to maintain density if (this.particles.length === 0) { - this.initParticles(); + this.initParticles(); } } @@ -71,9 +70,9 @@ export class ConstellationEffect { animate() { this.ctx.clearRect(0, 0, this.width, this.height); - + // Update and draw particles - this.particles.forEach(p => { + this.particles.forEach((p) => { p.update(this.width, this.height); p.draw(this.ctx); }); @@ -86,41 +85,41 @@ export class ConstellationEffect { private drawConnections() { this.ctx.lineWidth = 1; - + for (let i = 0; i < this.particles.length; i++) { - const p1 = this.particles[i]; - - // Connect to mouse if close - const distMouse = Math.hypot(p1.x - this.mouseX, p1.y - this.mouseY); - if (distMouse < this.connectionDistance + 50) { - const alpha = 1 - (distMouse / (this.connectionDistance + 50)); - this.ctx.strokeStyle = `rgba(255, 255, 255, ${alpha * 0.4})`; // Brighter near mouse - this.ctx.beginPath(); - this.ctx.moveTo(p1.x, p1.y); - this.ctx.lineTo(this.mouseX, this.mouseY); - this.ctx.stroke(); - - // Gently attract to mouse - if (distMouse > 10) { - p1.x += (this.mouseX - p1.x) * 0.005; - p1.y += (this.mouseY - p1.y) * 0.005; - } + const p1 = this.particles[i]; + + // Connect to mouse if close + const distMouse = Math.hypot(p1.x - this.mouseX, p1.y - this.mouseY); + if (distMouse < this.connectionDistance + 50) { + const alpha = 1 - distMouse / (this.connectionDistance + 50); + this.ctx.strokeStyle = `rgba(255, 255, 255, ${alpha * 0.4})`; // Brighter near mouse + this.ctx.beginPath(); + this.ctx.moveTo(p1.x, p1.y); + this.ctx.lineTo(this.mouseX, this.mouseY); + this.ctx.stroke(); + + // Gently attract to mouse + if (distMouse > 10) { + p1.x += (this.mouseX - p1.x) * 0.005; + p1.y += (this.mouseY - p1.y) * 0.005; } - - // Connect to other particles - for (let j = i + 1; j < this.particles.length; j++) { - const p2 = this.particles[j]; - const dist = Math.hypot(p1.x - p2.x, p1.y - p2.y); - - if (dist < this.connectionDistance) { - const alpha = 1 - (dist / this.connectionDistance); - this.ctx.strokeStyle = `rgba(255, 255, 255, ${alpha * 0.15})`; - this.ctx.beginPath(); - this.ctx.moveTo(p1.x, p1.y); - this.ctx.lineTo(p2.x, p2.y); - this.ctx.stroke(); - } + } + + // Connect to other particles + for (let j = i + 1; j < this.particles.length; j++) { + const p2 = this.particles[j]; + const dist = Math.hypot(p1.x - p2.x, p1.y - p2.y); + + if (dist < this.connectionDistance) { + const alpha = 1 - dist / this.connectionDistance; + this.ctx.strokeStyle = `rgba(255, 255, 255, ${alpha * 0.15})`; + this.ctx.beginPath(); + this.ctx.moveTo(p1.x, p1.y); + this.ctx.lineTo(p2.x, p2.y); + this.ctx.stroke(); } + } } } @@ -131,33 +130,33 @@ export class ConstellationEffect { } class Particle { - x: number; - y: number; - vx: number; - vy: number; - size: number; - - constructor(w: number, h: number, speed: number) { - this.x = Math.random() * w; - this.y = Math.random() * h; - this.vx = (Math.random() - 0.5) * speed; - this.vy = (Math.random() - 0.5) * speed; - this.size = Math.random() * 2 + 1; - } + x: number; + y: number; + vx: number; + vy: number; + size: number; + + constructor(w: number, h: number, speed: number) { + this.x = Math.random() * w; + this.y = Math.random() * h; + this.vx = (Math.random() - 0.5) * speed; + this.vy = (Math.random() - 0.5) * speed; + this.size = Math.random() * 2 + 1; + } - update(w: number, h: number) { - this.x += this.vx; - this.y += this.vy; + update(w: number, h: number) { + this.x += this.vx; + this.y += this.vy; - // Bounce off walls - if (this.x < 0 || this.x > w) this.vx *= -1; - if (this.y < 0 || this.y > h) this.vy *= -1; - } + // Bounce off walls + if (this.x < 0 || this.x > w) this.vx *= -1; + if (this.y < 0 || this.y > h) this.vy *= -1; + } - draw(ctx: CanvasRenderingContext2D) { - ctx.fillStyle = "rgba(255, 255, 255, 0.4)"; - ctx.beginPath(); - ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); - ctx.fill(); - } + draw(ctx: CanvasRenderingContext2D) { + ctx.fillStyle = "rgba(255, 255, 255, 0.4)"; + ctx.beginPath(); + ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); + ctx.fill(); + } } diff --git a/ui/src/lib/effects/SaturnEffect.ts b/ui/src/lib/effects/SaturnEffect.ts index 42aee66..357da9d 100644 --- a/ui/src/lib/effects/SaturnEffect.ts +++ b/ui/src/lib/effects/SaturnEffect.ts @@ -6,7 +6,7 @@ export class SaturnEffect { private ctx: CanvasRenderingContext2D; private width: number = 0; private height: number = 0; - + // Data-oriented design for performance // xyz: Float32Array where [i*3, i*3+1, i*3+2] corresponds to x, y, z private xyz: Float32Array | null = null; @@ -23,7 +23,7 @@ export class SaturnEffect { private lastMouseX: number = 0; private lastMouseTime: number = 0; private mouseVelocities: number[] = []; // Store recent velocities for averaging - + // Rotation speed control private readonly baseSpeed: number = 0.005; // Original rotation speed private currentSpeed: number = 0.005; // Current rotation speed (can be modified by mouse) @@ -35,15 +35,15 @@ export class SaturnEffect { constructor(canvas: HTMLCanvasElement) { this.canvas = canvas; - this.ctx = canvas.getContext('2d', { + this.ctx = canvas.getContext("2d", { alpha: true, - desynchronized: false // default is usually fine, 'desynchronized' can help latency but might flicker + desynchronized: false, // default is usually fine, 'desynchronized' can help latency but might flicker })!; - + // Initial resize will set up everything this.resize(window.innerWidth, window.innerHeight); this.initParticles(); - + this.animate = this.animate.bind(this); this.animate(); } @@ -60,24 +60,24 @@ export class SaturnEffect { handleMouseMove(clientX: number) { if (!this.isDragging) return; - + const currentTime = performance.now(); const deltaTime = currentTime - this.lastMouseTime; - + if (deltaTime > 0) { const deltaX = clientX - this.lastMouseX; const velocity = deltaX / deltaTime; // pixels per millisecond - + // Store recent velocities (keep last 5 for smoothing) this.mouseVelocities.push(velocity); if (this.mouseVelocities.length > 5) { this.mouseVelocities.shift(); } - + // Apply direct rotation while dragging this.angle += deltaX * 0.002; } - + this.lastMouseX = clientX; this.lastMouseTime = currentTime; } @@ -98,22 +98,22 @@ export class SaturnEffect { handleTouchMove(clientX: number) { if (!this.isDragging) return; - + const currentTime = performance.now(); const deltaTime = currentTime - this.lastMouseTime; - + if (deltaTime > 0) { const deltaX = clientX - this.lastMouseX; const velocity = deltaX / deltaTime; - + this.mouseVelocities.push(velocity); if (this.mouseVelocities.length > 5) { this.mouseVelocities.shift(); } - + this.angle += deltaX * 0.002; } - + this.lastMouseX = clientX; this.lastMouseTime = currentTime; } @@ -127,39 +127,40 @@ export class SaturnEffect { private applyFlingVelocity() { // Calculate average velocity from recent samples - const avgVelocity = this.mouseVelocities.reduce((a, b) => a + b, 0) / this.mouseVelocities.length; - + const avgVelocity = + this.mouseVelocities.reduce((a, b) => a + b, 0) / this.mouseVelocities.length; + // Threshold for considering it a "fling" (pixels per millisecond) const flingThreshold = 0.3; // Threshold for considering the rotation as "stopped" by user const stopThreshold = 0.1; - + if (Math.abs(avgVelocity) > flingThreshold) { // User flung it - start rotating again this.isStopped = false; - + // Determine new direction based on fling direction const newDirection = avgVelocity > 0 ? 1 : -1; - + // If direction changed, update it permanently if (newDirection !== this.rotationDirection) { this.rotationDirection = newDirection; } - + // Calculate speed boost based on fling strength // Map velocity to speed multiplier (stronger fling = faster rotation) const speedMultiplier = Math.min( this.maxSpeedMultiplier, - this.minSpeedMultiplier + Math.abs(avgVelocity) * 10 + this.minSpeedMultiplier + Math.abs(avgVelocity) * 10, ); - + this.currentSpeed = this.baseSpeed * speedMultiplier; } else if (Math.abs(avgVelocity) < stopThreshold) { // User gently released - keep it stopped this.isStopped = true; this.currentSpeed = 0; } - // If velocity is between stopThreshold and flingThreshold, + // If velocity is between stopThreshold and flingThreshold, // keep current state (don't change isStopped) } @@ -167,17 +168,17 @@ export class SaturnEffect { const dpr = window.devicePixelRatio || 1; this.width = width; this.height = height; - + this.canvas.width = width * dpr; this.canvas.height = height * dpr; this.canvas.style.width = `${width}px`; this.canvas.style.height = `${height}px`; - + this.ctx.scale(dpr, dpr); // Dynamic scaling based on screen size const minDim = Math.min(width, height); - this.scaleFactor = minDim * 0.45; + this.scaleFactor = minDim * 0.45; } initParticles() { @@ -197,65 +198,68 @@ export class SaturnEffect { // 1. Planet for (let i = 0; i < planetCount; i++) { - const theta = Math.random() * Math.PI * 2; - const phi = Math.acos((Math.random() * 2) - 1); - const r = 1.0; - - // x, y, z - this.xyz[idx * 3] = r * Math.sin(phi) * Math.cos(theta); - this.xyz[idx * 3 + 1] = r * Math.sin(phi) * Math.sin(theta); - this.xyz[idx * 3 + 2] = r * Math.cos(phi); - - this.types[idx] = 0; // 0 for planet - idx++; + const theta = Math.random() * Math.PI * 2; + const phi = Math.acos(Math.random() * 2 - 1); + const r = 1.0; + + // x, y, z + this.xyz[idx * 3] = r * Math.sin(phi) * Math.cos(theta); + this.xyz[idx * 3 + 1] = r * Math.sin(phi) * Math.sin(theta); + this.xyz[idx * 3 + 2] = r * Math.cos(phi); + + this.types[idx] = 0; // 0 for planet + idx++; } // 2. Rings - const ringInner = 1.4; + const ringInner = 1.4; const ringOuter = 2.3; - + for (let i = 0; i < ringCount; i++) { - const angle = Math.random() * Math.PI * 2; - const dist = Math.sqrt(Math.random() * (ringOuter*ringOuter - ringInner*ringInner) + ringInner*ringInner); - - // x, y, z - this.xyz[idx * 3] = dist * Math.cos(angle); - this.xyz[idx * 3 + 1] = (Math.random() - 0.5) * 0.05; - this.xyz[idx * 3 + 2] = dist * Math.sin(angle); - - this.types[idx] = 1; // 1 for ring - idx++; + const angle = Math.random() * Math.PI * 2; + const dist = Math.sqrt( + Math.random() * (ringOuter * ringOuter - ringInner * ringInner) + ringInner * ringInner, + ); + + // x, y, z + this.xyz[idx * 3] = dist * Math.cos(angle); + this.xyz[idx * 3 + 1] = (Math.random() - 0.5) * 0.05; + this.xyz[idx * 3 + 2] = dist * Math.sin(angle); + + this.types[idx] = 1; // 1 for ring + idx++; } } animate() { this.ctx.clearRect(0, 0, this.width, this.height); - + // Normal blending - this.ctx.globalCompositeOperation = 'source-over'; - + this.ctx.globalCompositeOperation = "source-over"; + // Update rotation speed - decay towards base speed while maintaining direction if (!this.isDragging && !this.isStopped) { if (this.currentSpeed > this.baseSpeed) { // Gradually decay speed back to base speed - this.currentSpeed = this.baseSpeed + (this.currentSpeed - this.baseSpeed) * this.speedDecayRate; - + this.currentSpeed = + this.baseSpeed + (this.currentSpeed - this.baseSpeed) * this.speedDecayRate; + // Snap to base speed when close enough if (this.currentSpeed - this.baseSpeed < 0.00001) { this.currentSpeed = this.baseSpeed; } } - + // Apply rotation with current speed and direction this.angle += this.currentSpeed * this.rotationDirection; } - const cx = this.width * 0.6; + const cx = this.width * 0.6; const cy = this.height * 0.5; - + // Pre-calculate rotation matrices const rotationY = this.angle; - const rotationX = 0.4; + const rotationX = 0.4; const rotationZ = 0.15; const sinY = Math.sin(rotationY); @@ -265,66 +269,66 @@ export class SaturnEffect { const sinZ = Math.sin(rotationZ); const cosZ = Math.cos(rotationZ); - const fov = 1500; + const fov = 1500; const scaleFactor = this.scaleFactor; if (!this.xyz || !this.types) return; for (let i = 0; i < this.count; i++) { - const x = this.xyz[i * 3]; - const y = this.xyz[i * 3 + 1]; - const z = this.xyz[i * 3 + 2]; - - // Apply Scale - const px = x * scaleFactor; - const py = y * scaleFactor; - const pz = z * scaleFactor; - - // 1. Rotate Y - const x1 = px * cosY - pz * sinY; - const z1 = pz * cosY + px * sinY; - // y1 = py - - // 2. Rotate X - const y2 = py * cosX - z1 * sinX; - const z2 = z1 * cosX + py * sinX; - // x2 = x1 - - // 3. Rotate Z - const x3 = x1 * cosZ - y2 * sinZ; - const y3 = y2 * cosZ + x1 * sinZ; - const z3 = z2; - - const scale = fov / (fov + z3); - - if (z3 > -fov) { - const x2d = cx + x3 * scale; - const y2d = cy + y3 * scale; - - // Size calculation - slightly larger dots to compensate for lower count - // Previously Planet 2.0 -> 2.4, Ring 1.3 -> 1.5 - const type = this.types[i]; - const sizeBase = type === 0 ? 2.4 : 1.5; - const size = sizeBase * scale; - - // Opacity - let alpha = (scale * scale * scale); - if (alpha > 1) alpha = 1; - if (alpha < 0.15) continue; // Skip very faint particles for performance - - // Optimization: Planet color vs Ring color - if (type === 0) { - // Planet: Warm White - this.ctx.fillStyle = `rgba(255, 240, 220, ${alpha})`; - } else { - // Ring: Cool White - this.ctx.fillStyle = `rgba(220, 240, 255, ${alpha})`; - } - - // Render as squares (fillRect) instead of circles (arc) - // This is significantly faster for software rendering and reduces GPU usage. - this.ctx.fillRect(x2d, y2d, size, size); + const x = this.xyz[i * 3]; + const y = this.xyz[i * 3 + 1]; + const z = this.xyz[i * 3 + 2]; + + // Apply Scale + const px = x * scaleFactor; + const py = y * scaleFactor; + const pz = z * scaleFactor; + + // 1. Rotate Y + const x1 = px * cosY - pz * sinY; + const z1 = pz * cosY + px * sinY; + // y1 = py + + // 2. Rotate X + const y2 = py * cosX - z1 * sinX; + const z2 = z1 * cosX + py * sinX; + // x2 = x1 + + // 3. Rotate Z + const x3 = x1 * cosZ - y2 * sinZ; + const y3 = y2 * cosZ + x1 * sinZ; + const z3 = z2; + + const scale = fov / (fov + z3); + + if (z3 > -fov) { + const x2d = cx + x3 * scale; + const y2d = cy + y3 * scale; + + // Size calculation - slightly larger dots to compensate for lower count + // Previously Planet 2.0 -> 2.4, Ring 1.3 -> 1.5 + const type = this.types[i]; + const sizeBase = type === 0 ? 2.4 : 1.5; + const size = sizeBase * scale; + + // Opacity + let alpha = scale * scale * scale; + if (alpha > 1) alpha = 1; + if (alpha < 0.15) continue; // Skip very faint particles for performance + + // Optimization: Planet color vs Ring color + if (type === 0) { + // Planet: Warm White + this.ctx.fillStyle = `rgba(255, 240, 220, ${alpha})`; + } else { + // Ring: Cool White + this.ctx.fillStyle = `rgba(220, 240, 255, ${alpha})`; } + + // Render as squares (fillRect) instead of circles (arc) + // This is significantly faster for software rendering and reduces GPU usage. + this.ctx.fillRect(x2d, y2d, size, size); + } } this.animationId = requestAnimationFrame(this.animate); @@ -334,4 +338,3 @@ export class SaturnEffect { cancelAnimationFrame(this.animationId); } } - diff --git a/ui/src/lib/modLoaderApi.ts b/ui/src/lib/modLoaderApi.ts index 9d0d09d..75f404a 100644 --- a/ui/src/lib/modLoaderApi.ts +++ b/ui/src/lib/modLoaderApi.ts @@ -34,7 +34,7 @@ export async function getFabricLoaderVersions(): Promise<FabricLoaderVersion[]> * Get Fabric loaders available for a specific Minecraft version. */ export async function getFabricLoadersForVersion( - gameVersion: string + gameVersion: string, ): Promise<FabricLoaderEntry[]> { return invoke<FabricLoaderEntry[]>("get_fabric_loaders_for_version", { gameVersion, @@ -46,7 +46,7 @@ export async function getFabricLoadersForVersion( */ export async function installFabric( gameVersion: string, - loaderVersion: string + loaderVersion: string, ): Promise<InstalledFabricVersion> { return invoke<InstalledFabricVersion>("install_fabric", { gameVersion, @@ -66,7 +66,7 @@ export async function listInstalledFabricVersions(): Promise<string[]> { */ export async function isFabricInstalled( gameVersion: string, - loaderVersion: string + loaderVersion: string, ): Promise<boolean> { return invoke<boolean>("is_fabric_installed", { gameVersion, @@ -86,9 +86,7 @@ export async function getForgeGameVersions(): Promise<string[]> { /** * Get Forge versions available for a specific Minecraft version. */ -export async function getForgeVersionsForGame( - gameVersion: string -): Promise<ForgeVersion[]> { +export async function getForgeVersionsForGame(gameVersion: string): Promise<ForgeVersion[]> { return invoke<ForgeVersion[]>("get_forge_versions_for_game", { gameVersion, }); @@ -99,7 +97,7 @@ export async function getForgeVersionsForGame( */ export async function installForge( gameVersion: string, - forgeVersion: string + forgeVersion: string, ): Promise<InstalledForgeVersion> { return invoke<InstalledForgeVersion>("install_forge", { gameVersion, |