aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
408 lines (405 loc) • 12 kB
JavaScript
import { useRef, useEffect } from 'react';
class GalileoPhysicsSystem {
constructor(config = {}) {
this.animationFrame = null;
this.lastTime = 0;
this.paused = false;
this.animate = () => {
const currentTime = performance.now();
const deltaTime = Math.min((currentTime - this.lastTime) / 1000, 1 / 30); // Cap at 30 FPS minimum
if (!this.paused) {
for (let i = 0; i < this.world.substeps; i++) {
this.step(deltaTime / this.world.substeps);
}
}
this.lastTime = currentTime;
this.animationFrame = requestAnimationFrame(this.animate);
};
this.world = {
bodies: new Map(),
gravity: {
x: 0,
y: 0
},
bounds: {
left: -Infinity,
right: Infinity,
top: -Infinity,
bottom: Infinity
},
timeStep: 1 / 60,
// 60 FPS
substeps: 1,
...config
};
}
// Body management
addBody(body) {
this.world.bodies.set(body.id, body);
}
removeBody(id) {
this.world.bodies.delete(id);
}
getBody(id) {
return this.world.bodies.get(id);
}
updateBody(id, updates) {
const body = this.world.bodies.get(id);
if (body) {
Object.assign(body, updates);
}
}
// Force management
applyForce(id, force, duration) {
const body = this.world.bodies.get(id);
if (body) {
body.forces.push({
...force,
duration
});
}
}
applyImpulse(id, impulse) {
const body = this.world.bodies.get(id);
if (body && !body.fixed) {
body.velocity.x += impulse.x / body.mass;
body.velocity.y += impulse.y / body.mass;
}
}
// Physics simulation
start() {
if (this.animationFrame) return;
this.lastTime = performance.now();
if (this.animate) this.animate();
}
stop() {
if (this.animationFrame) {
cancelAnimationFrame(this.animationFrame);
this.animationFrame = null;
}
}
pause() {
this.paused = true;
}
resume() {
this.paused = false;
this.lastTime = performance.now();
}
step(deltaTime) {
// Update forces
this.updateForces(deltaTime);
// Integrate motion
this.integrateMotion(deltaTime);
// Handle collisions
this.handleCollisions();
// Apply constraints
this.applyConstraints();
}
updateForces(deltaTime) {
this.world.bodies.forEach(body => {
if (body.fixed) return;
// Reset acceleration
body.acceleration.x = 0;
body.acceleration.y = 0;
// Apply gravity
body.acceleration.x += this.world.gravity.x;
body.acceleration.y += this.world.gravity.y;
// Apply custom forces
body.forces = body.forces.filter(force => {
body.acceleration.x += force.x / body.mass;
body.acceleration.y += force.y / body.mass;
if (force.duration !== undefined) {
force.duration -= deltaTime;
return force.duration > 0;
}
return true; // Permanent forces
});
// Apply friction
const speed = Math.sqrt(body.velocity.x ** 2 + body.velocity.y ** 2);
if (speed > 0.001) {
const frictionForce = body.friction * body.mass * Math.abs(this.world.gravity.y || 1);
const frictionX = -body.velocity.x / speed * frictionForce / body.mass;
const frictionY = -body.velocity.y / speed * frictionForce / body.mass;
body.acceleration.x += frictionX;
body.acceleration.y += frictionY;
}
});
}
integrateMotion(deltaTime) {
this.world.bodies.forEach(body => {
if (body.fixed) return;
// Verlet integration for better stability
const newVelocityX = body.velocity.x + body.acceleration.x * deltaTime;
const newVelocityY = body.velocity.y + body.acceleration.y * deltaTime;
body.position.x += (body.velocity.x + newVelocityX) * 0.5 * deltaTime;
body.position.y += (body.velocity.y + newVelocityY) * 0.5 * deltaTime;
body.velocity.x = newVelocityX;
body.velocity.y = newVelocityY;
});
}
handleCollisions() {
const bodies = Array.from(this.world.bodies.values());
const collisions = [];
// Check all pairs for collisions
for (let i = 0; i < bodies.length; i++) {
for (let j = i + 1; j < bodies.length; j++) {
const collision = this.checkCollision(bodies[i], bodies[j]);
if (collision) {
collisions.push(collision);
}
}
}
// Resolve collisions
collisions.forEach(collision => {
this.resolveCollision(collision);
});
}
checkCollision(bodyA, bodyB) {
const dx = bodyB.position.x - bodyA.position.x;
const dy = bodyB.position.y - bodyA.position.y;
const distance = Math.sqrt(dx * dx + dy * dy);
const minDistance = bodyA.radius + bodyB.radius;
if (distance < minDistance) {
const penetration = minDistance - distance;
const normal = {
x: dx / distance,
y: dy / distance
};
return {
bodyA,
bodyB,
normal,
penetration,
contactPoint: {
x: bodyA.position.x + normal.x * bodyA.radius,
y: bodyA.position.y + normal.y * bodyA.radius
}
};
}
return null;
}
resolveCollision(collision) {
const {
bodyA,
bodyB,
normal,
penetration
} = collision;
// Separate bodies
const separationX = normal.x * penetration * 0.5;
const separationY = normal.y * penetration * 0.5;
if (!bodyA.fixed) {
bodyA.position.x -= separationX;
bodyA.position.y -= separationY;
}
if (!bodyB.fixed) {
bodyB.position.x += separationX;
bodyB.position.y += separationY;
}
// Calculate relative velocity
const relativeVelocityX = bodyB.velocity.x - bodyA.velocity.x;
const relativeVelocityY = bodyB.velocity.y - bodyA.velocity.y;
const velocityAlongNormal = relativeVelocityX * normal.x + relativeVelocityY * normal.y;
// Don't resolve if velocities are separating
if (velocityAlongNormal > 0) return;
// Calculate restitution
const restitution = Math.min(bodyA.restitution, bodyB.restitution);
// Calculate impulse scalar
const impulseScalar = -(1 + restitution) * velocityAlongNormal;
const totalMass = bodyA.mass + bodyB.mass;
let impulseX = impulseScalar * normal.x;
let impulseY = impulseScalar * normal.y;
// Apply mass weighting
if (!bodyA.fixed && !bodyB.fixed) {
impulseX /= totalMass / bodyA.mass;
impulseY /= totalMass / bodyA.mass;
}
// Apply impulses
if (!bodyA.fixed) {
bodyA.velocity.x -= impulseX / bodyA.mass;
bodyA.velocity.y -= impulseY / bodyA.mass;
}
if (!bodyB.fixed) {
bodyB.velocity.x += impulseX / bodyB.mass;
bodyB.velocity.y += impulseY / bodyB.mass;
}
}
applyConstraints() {
this.world.bodies.forEach(body => {
if (body.fixed) return;
// Boundary constraints
const {
bounds
} = this.world;
if (body.position.x - body.radius < bounds.left) {
body.position.x = bounds.left + body.radius;
body.velocity.x *= -body.restitution;
}
if (body.position.x + body.radius > bounds.right) {
body.position.x = bounds.right - body.radius;
body.velocity.x *= -body.restitution;
}
if (body.position.y - body.radius < bounds.top) {
body.position.y = bounds.top + body.radius;
body.velocity.y *= -body.restitution;
}
if (body.position.y + body.radius > bounds.bottom) {
body.position.y = bounds.bottom - body.radius;
body.velocity.y *= -body.restitution;
}
});
}
// Utility methods
setGravity(gravity) {
this.world.gravity = gravity;
}
setBounds(bounds) {
this.world.bounds = {
...this.world.bounds,
...bounds
};
}
getAllBodies() {
return Array.from(this.world.bodies.values());
}
clear() {
this.world.bodies.clear();
}
// Factory methods for common body types
static createCircleBody(id, x, y, radius, mass = 1, options = {}) {
return {
id,
position: {
x,
y
},
velocity: {
x: 0,
y: 0
},
acceleration: {
x: 0,
y: 0
},
mass,
radius,
friction: 0.1,
restitution: 0.8,
fixed: false,
forces: [],
...options
};
}
static createFixedBody(id, x, y, radius) {
return {
id,
position: {
x,
y
},
velocity: {
x: 0,
y: 0
},
acceleration: {
x: 0,
y: 0
},
mass: Infinity,
radius,
friction: 0,
restitution: 1,
fixed: true,
forces: []
};
}
}
// Hook for using physics system
function usePhysicsSystem(config) {
const systemRef = useRef();
useEffect(() => {
systemRef.current = new GalileoPhysicsSystem(config);
return () => {
systemRef.current?.stop();
};
}, []);
return systemRef.current;
}
// Spring physics utilities
class SpringPhysics {
static calculateSpringForce(current, target, velocity, config) {
const displacement = target - current;
const springForce = displacement * (config.stiffness ?? 100);
const dampingForce = -velocity * (config.damping ?? 10);
const totalForce = springForce + dampingForce;
const acceleration = totalForce / (config.mass ?? 1);
const newVelocity = velocity + acceleration * 0.016; // Assuming 60fps
return {
force: totalForce,
newVelocity
};
}
static interpolateSpring(from, to, progress, config) {
// Simplified spring interpolation
const t = progress;
const stiffness = config.stiffness || 100;
const damping = config.damping || 10;
// Critically damped spring
const omega = Math.sqrt(stiffness);
const zeta = damping / (2 * Math.sqrt(stiffness));
if (zeta >= 1) {
// Over-damped
const r1 = -omega * (zeta - Math.sqrt(zeta * zeta - 1));
const r2 = -omega * (zeta + Math.sqrt(zeta * zeta - 1));
return to + (from - to) * (r1 * Math.exp(r1 * t) - r2 * Math.exp(r2 * t)) / (r1 - r2);
} else {
// Under-damped
const alpha = omega * Math.sqrt(1 - zeta * zeta);
const beta = omega * zeta;
return to + (from - to) * Math.exp(-beta * t) * Math.cos(alpha * t);
}
}
}
// Advanced physics utilities
const physicsUtils = {
// Calculate trajectory
calculateTrajectory: (initialPosition, initialVelocity, gravity, time) => {
return {
x: initialPosition.x + initialVelocity.x * time + 0.5 * gravity.x * time * time,
y: initialPosition.y + initialVelocity.y * time + 0.5 * gravity.y * time * time
};
},
// Calculate collision point
calculateCollisionPoint: (bodyA, bodyB) => {
const dx = bodyB.position.x - bodyA.position.x;
const dy = bodyB.position.y - bodyA.position.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < bodyA.radius + bodyB.radius) {
const overlap = bodyA.radius + bodyB.radius - distance;
return {
x: bodyA.position.x + dx / distance * (bodyA.radius - overlap / 2),
y: bodyA.position.y + dy / distance * (bodyA.radius - overlap / 2)
};
}
return null;
},
// Apply explosion force
applyExplosion: (bodies, center, force, radius) => {
bodies.forEach(body => {
const dx = body.position.x - center.x;
const dy = body.position.y - center.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < radius && distance > 0) {
const strength = (1 - distance / radius) * force;
const impulse = {
x: dx / distance * strength / body.mass,
y: dy / distance * strength / body.mass
};
body.velocity.x += impulse.x;
body.velocity.y += impulse.y;
}
});
}
};
export { GalileoPhysicsSystem, SpringPhysics, physicsUtils, usePhysicsSystem };
//# sourceMappingURL=galileoPhysicsSystem.js.map