mirror of
https://github.com/101island/lolisland.us.git
synced 2026-03-01 11:49:43 +08:00
perf: optimize marble physics via spatial grid and reduce CSS rendering overhead
This commit is contained in:
@@ -140,15 +140,13 @@ import Particles from "./Particles.astro";
|
||||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||
background: linear-gradient(
|
||||
145deg,
|
||||
rgba(255, 255, 255, 0.06),
|
||||
rgba(255, 255, 255, 0.03)
|
||||
rgba(255, 255, 255, 0.12),
|
||||
rgba(255, 255, 255, 0.04)
|
||||
);
|
||||
backdrop-filter: blur(16px);
|
||||
backdrop-filter: blur(6px);
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(255, 255, 255, 0.08),
|
||||
0 30px 80px rgba(0, 0, 0, 0.35),
|
||||
0 0 60px rgba(255, 127, 209, 0.28),
|
||||
0 0 40px rgba(124, 251, 255, 0.24);
|
||||
0 10px 40px rgba(0, 0, 0, 0.5);
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
max-width: 80vw;
|
||||
@@ -241,13 +239,12 @@ import Particles from "./Particles.astro";
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
box-shadow:
|
||||
inset -3px -5px 10px rgba(0, 0, 0, 0.5),
|
||||
inset 2px 4px 6px rgba(255, 255, 255, 0.35),
|
||||
0 0 14px rgba(255, 255, 255, 0.45),
|
||||
0 0 4px rgba(255, 255, 255, 0.6),
|
||||
0 8px 20px rgba(0, 0, 0, 0.4);
|
||||
backdrop-filter: blur(4px);
|
||||
inset -2px -2px 6px rgba(0, 0, 0, 0.6),
|
||||
inset 2px 2px 4px rgba(255, 255, 255, 0.4),
|
||||
0 2px 8px rgba(0, 0, 0, 0.4);
|
||||
/* backdrop-filter: blur(4px); -- Removed for performance */
|
||||
overflow: hidden;
|
||||
transform: translate3d(0, 0, 0);
|
||||
will-change: transform;
|
||||
@@ -271,11 +268,9 @@ import Particles from "./Particles.astro";
|
||||
:global(.marble:hover) {
|
||||
transform: scale(1.15) translateY(-2px);
|
||||
box-shadow:
|
||||
inset -3px -5px 12px rgba(0, 0, 0, 0.4),
|
||||
inset 2px 4px 8px rgba(255, 255, 255, 0.45),
|
||||
0 0 25px rgba(255, 255, 255, 0.65),
|
||||
0 0 8px rgba(255, 255, 255, 0.8),
|
||||
0 15px 35px rgba(0, 0, 0, 0.45);
|
||||
inset -2px -2px 6px rgba(0, 0, 0, 0.5),
|
||||
inset 2px 2px 6px rgba(255, 255, 255, 0.5),
|
||||
0 4px 12px rgba(0, 0, 0, 0.5);
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
|
||||
@@ -292,8 +287,8 @@ import Particles from "./Particles.astro";
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
text-align: center;
|
||||
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.6);
|
||||
backdrop-filter: blur(6px);
|
||||
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8);
|
||||
/* backdrop-filter: blur(6px); -- Removed for performance */
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,40 +62,116 @@ export class MarblePhysics {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle collisions between marbles
|
||||
// Handle collisions between marbles using Spatial Grid
|
||||
public handleCollisions(marbles: Marble[]): void {
|
||||
const restitution = this.config.restitution ?? 1;
|
||||
const { fieldWidth, fieldHeight } = this.config;
|
||||
|
||||
for (let i = 0; i < marbles.length; i++) {
|
||||
for (let j = i + 1; j < marbles.length; j++) {
|
||||
const a = marbles[i];
|
||||
const b = marbles[j];
|
||||
const dx = b.x - a.x;
|
||||
const dy = b.y - a.y;
|
||||
const dist = Math.hypot(dx, dy) || 0.001;
|
||||
const minDist = a.radius + b.radius;
|
||||
// 1. Determine grid cell size
|
||||
// Using the maximum diameter of any marble ensures we only need to check adjacent cells.
|
||||
// If marbles can vary wildly in size, this might need tuning, but max diameter is safe.
|
||||
let maxDiameter = 0;
|
||||
for (const m of marbles) {
|
||||
if (m.radius * 2 > maxDiameter) maxDiameter = m.radius * 2;
|
||||
}
|
||||
// Fallback if no marbles or something goes wrong
|
||||
if (maxDiameter === 0) return;
|
||||
|
||||
if (dist < minDist) {
|
||||
const nx = dx / dist;
|
||||
const ny = dy / dist;
|
||||
const relativeVelocity = (a.vx - b.vx) * nx + (a.vy - b.vy) * ny;
|
||||
const cellSize = maxDiameter;
|
||||
const gridWidth = Math.ceil(fieldWidth / cellSize);
|
||||
const gridHeight = Math.ceil(fieldHeight / cellSize);
|
||||
|
||||
if (relativeVelocity < 0) {
|
||||
// Calculate impulse using restitution coefficient
|
||||
const impulse =
|
||||
((1 + restitution) * relativeVelocity) / (a.mass + b.mass);
|
||||
a.vx -= impulse * b.mass * nx;
|
||||
a.vy -= impulse * b.mass * ny;
|
||||
b.vx += impulse * a.mass * nx;
|
||||
b.vy += impulse * a.mass * ny;
|
||||
// 2. Build the grid
|
||||
// Map: cellIndex -> Particle[]
|
||||
const grid = new Map<number, Marble[]>();
|
||||
|
||||
const getGridIndex = (x: number, y: number) => {
|
||||
const gx = Math.floor(x / cellSize);
|
||||
const gy = Math.floor(y / cellSize);
|
||||
// Clamp to valid range to handle out-of-bounds marbles gracefully
|
||||
if (gx < 0 || gx >= gridWidth || gy < 0 || gy >= gridHeight) return -1;
|
||||
return gx + gy * gridWidth;
|
||||
};
|
||||
|
||||
for (const m of marbles) {
|
||||
const index = getGridIndex(m.x, m.y);
|
||||
if (index === -1) continue; // Skip out of bounds marbles (handled by boundaries)
|
||||
|
||||
if (!grid.has(index)) {
|
||||
grid.set(index, []);
|
||||
}
|
||||
grid.get(index)?.push(m);
|
||||
}
|
||||
|
||||
// 3. Check collisions (Grid-based)
|
||||
// We iterate through each marble, find its cell, and check that cell + neighbors
|
||||
for (const i_marble of marbles) {
|
||||
const gx = Math.floor(i_marble.x / cellSize);
|
||||
const gy = Math.floor(i_marble.y / cellSize);
|
||||
|
||||
// Check 3x3 neighbors (including own cell)
|
||||
// Optimization: We could only check "forward" cells to avoid double checks,
|
||||
// but since we need to resolve for both, and the grid logic is simpler to iterate neighbors:
|
||||
// Standard way to avoid double checking A vs B and B vs A is to check all neighbors
|
||||
// and only resolve if ID(A) < ID(B) or similar check.
|
||||
|
||||
for (let nx = gx - 1; nx <= gx + 1; nx++) {
|
||||
for (let ny = gy - 1; ny <= gy + 1; ny++) {
|
||||
if (nx < 0 || nx >= gridWidth || ny < 0 || ny >= gridHeight) continue;
|
||||
|
||||
const neighborIndex = nx + ny * gridWidth;
|
||||
const cellMarbles = grid.get(neighborIndex);
|
||||
|
||||
if (!cellMarbles) continue;
|
||||
|
||||
for (const j_marble of cellMarbles) {
|
||||
// Avoid self-collision
|
||||
if (i_marble === j_marble) continue;
|
||||
|
||||
// Avoid double checking: only check if index(i) < index(j)
|
||||
// But here we rely on the object content.
|
||||
// Since marbles is an array, we can check if marbles.indexOf(i) < marbles.indexOf(j)?
|
||||
// That's O(N) inside loop.
|
||||
// Instead, let's just do the check and rely on the fact that if we resolve A vs B,
|
||||
// we might resolve B vs A later.
|
||||
// Ideally: We iterate unique pairs.
|
||||
// Optimization: Only check half-neighborhood?
|
||||
// Or simpler: check all, but only act if i_marble.id < j_marble.id
|
||||
if (i_marble.id >= j_marble.id) continue;
|
||||
|
||||
const a = i_marble;
|
||||
const b = j_marble;
|
||||
|
||||
const dx = b.x - a.x;
|
||||
const dy = b.y - a.y;
|
||||
const distSq = dx * dx + dy * dy;
|
||||
const minDist = a.radius + b.radius;
|
||||
const minDistSq = minDist * minDist;
|
||||
|
||||
if (distSq < minDistSq) {
|
||||
const dist = Math.sqrt(distSq) || 0.001;
|
||||
const nx = dx / dist;
|
||||
const ny = dy / dist;
|
||||
const relativeVelocity = (a.vx - b.vx) * nx + (a.vy - b.vy) * ny;
|
||||
|
||||
if (relativeVelocity < 0) {
|
||||
// Calculate impulse using restitution coefficient
|
||||
const impulse =
|
||||
((1 + restitution) * relativeVelocity) / (a.mass + b.mass);
|
||||
a.vx -= impulse * b.mass * nx;
|
||||
a.vy -= impulse * b.mass * ny;
|
||||
b.vx += impulse * a.mass * nx;
|
||||
b.vy += impulse * a.mass * ny;
|
||||
}
|
||||
|
||||
// Position correction to prevent overlap
|
||||
const overlap = minDist - dist + 0.5;
|
||||
a.x -= nx * overlap * 0.5;
|
||||
a.y -= ny * overlap * 0.5;
|
||||
b.x += nx * overlap * 0.5;
|
||||
b.y += ny * overlap * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
// Position correction to prevent overlap
|
||||
const overlap = minDist - dist + 0.5;
|
||||
a.x -= nx * overlap * 0.5;
|
||||
a.y -= ny * overlap * 0.5;
|
||||
b.x += nx * overlap * 0.5;
|
||||
b.y += ny * overlap * 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user