aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
412 lines (409 loc) • 12.7 kB
JavaScript
'use client';
import { useRef, useState, useEffect, useCallback } from 'react';
import { GalileoPhysicsSystem } from '../../animations/physics/galileoPhysicsSystem.js';
const DEFAULT_CONFIG = {
stiffness: 100,
damping: 10,
mass: 1,
precision: 0.01,
velocity: 0,
gravity: false,
gravityStrength: 9.81,
enableCollisions: false,
bounds: {
left: -Infinity,
right: Infinity,
top: -Infinity,
bottom: Infinity
}
};
function useGalileoSprings(targets = [], config = {}) {
const finalConfig = {
...DEFAULT_CONFIG,
...config
};
const physicsSystemRef = useRef();
const [positions, setPositions] = useState({});
const [isAnimating, setIsAnimating] = useState(false);
// Initialize physics system
useEffect(() => {
physicsSystemRef.current = new GalileoPhysicsSystem({
gravity: finalConfig.gravity ? {
x: 0,
y: finalConfig.gravityStrength
} : {
x: 0,
y: 0
},
bounds: finalConfig.bounds
});
// Add initial bodies
targets.forEach(target => {
physicsSystemRef.current.addBody({
id: target.id,
position: {
...target.position
},
velocity: target.velocity || {
x: 0,
y: 0
},
acceleration: {
x: 0,
y: 0
},
mass: target.mass || finalConfig.mass,
radius: target.radius || 10,
friction: 0.1,
restitution: 0.8,
fixed: target.fixed || false,
forces: []
});
});
physicsSystemRef.current.start();
return () => {
physicsSystemRef.current?.stop();
};
}, [targets, finalConfig]);
// Update positions when physics system updates
useEffect(() => {
if (!physicsSystemRef.current) return;
const updatePositions = () => {
const bodies = physicsSystemRef.current.getAllBodies();
const newPositions = {};
bodies.forEach(body => {
newPositions[body.id] = {
...body.position
};
});
setPositions(newPositions);
// Check if any bodies are still moving
const hasMovingBodies = bodies.some(body => Math.abs(body.velocity.x) > finalConfig.precision || Math.abs(body.velocity.y) > finalConfig.precision);
setIsAnimating(hasMovingBodies);
if (hasMovingBodies) {
requestAnimationFrame(updatePositions);
}
};
updatePositions();
}, [finalConfig.precision]);
// Apply spring force to move body to target
const springTo = useCallback((id, target, customConfig) => {
if (!physicsSystemRef.current) return;
const body = physicsSystemRef.current.getBody(id);
if (!body) return;
const springConfig = {
...finalConfig,
...customConfig
};
// Calculate spring force
const dx = target.x - body.position.x;
const dy = target.y - body.position.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > springConfig.precision) {
const force = {
x: dx * springConfig.stiffness - body.velocity.x * springConfig.damping,
y: dy * springConfig.stiffness - body.velocity.y * springConfig.damping
};
physicsSystemRef.current.applyForce(id, force, 1 / 60); // Apply for one frame
}
}, [finalConfig]);
// Apply impulse to body
const impulse = useCallback((id, impulse) => {
if (!physicsSystemRef.current) return;
physicsSystemRef.current.applyImpulse(id, impulse);
}, []);
// Set body position directly
const setPosition = useCallback((id, position) => {
if (!physicsSystemRef.current) return;
const body = physicsSystemRef.current.getBody(id);
if (body) {
body.position = {
...position
};
body.velocity = {
x: 0,
y: 0
};
body.acceleration = {
x: 0,
y: 0
};
}
}, []);
// Add new body to physics system
const addBody = useCallback(body => {
if (!physicsSystemRef.current) return;
physicsSystemRef.current.addBody({
id: body.id,
position: {
...body.position
},
velocity: body.velocity || {
x: 0,
y: 0
},
acceleration: {
x: 0,
y: 0
},
mass: body.mass || finalConfig.mass,
radius: body.radius || 10,
friction: 0.1,
restitution: 0.8,
fixed: body.fixed || false,
forces: []
});
}, [finalConfig.mass]);
// Remove body from physics system
const removeBody = useCallback(id => {
if (!physicsSystemRef.current) return;
physicsSystemRef.current.removeBody(id);
}, []);
// Get body information
const getBody = useCallback(id => {
if (!physicsSystemRef.current) return;
return physicsSystemRef.current.getBody(id);
}, []);
// Update physics configuration
const updateConfig = useCallback(newConfig => {
Object.assign(finalConfig, newConfig);
if (physicsSystemRef.current) {
physicsSystemRef.current.setGravity(finalConfig.gravity ? {
x: 0,
y: finalConfig.gravityStrength
} : {
x: 0,
y: 0
});
physicsSystemRef.current.setBounds(finalConfig.bounds);
}
}, [finalConfig]);
// Batch operations
const batchUpdate = useCallback(updates => {
updates.forEach(update => {
switch (update.type) {
case 'springTo':
if (update.data) springTo(update.id, update.data);
break;
case 'impulse':
if (update.data) impulse(update.id, update.data);
break;
case 'setPosition':
if (update.data) setPosition(update.id, update.data);
break;
case 'addBody':
if (update.data) addBody(update.data);
break;
case 'removeBody':
removeBody(update.id);
break;
}
});
}, [springTo, impulse, setPosition, addBody, removeBody]);
return {
positions,
isAnimating,
springTo,
impulse,
setPosition,
addBody,
removeBody,
getBody,
updateConfig,
batchUpdate,
physicsSystem: physicsSystemRef.current
};
}
// Hook for chain reactions (bodies affecting each other)
function useChainReaction(bodies, connections, config = {}) {
const galileoSprings = useGalileoSprings(bodies, config);
const triggerChainReaction = useCallback((startId, initialImpulse) => {
// Apply initial impulse
galileoSprings.impulse(startId, initialImpulse);
// Propagate through connections
const visited = new Set();
const queue = [startId];
const propagate = () => {
if (queue.length === 0) return;
const currentId = queue.shift();
if (visited.has(currentId)) return;
visited.add(currentId);
// Find connections from current body
const currentConnections = connections.filter(conn => conn.from === currentId);
currentConnections.forEach(connection => {
if (!visited.has(connection.to)) {
// Calculate propagated impulse
const currentBody = galileoSprings.getBody(currentId);
if (currentBody) {
const propagatedImpulse = {
x: currentBody.velocity.x * connection.strength,
y: currentBody.velocity.y * connection.strength
};
galileoSprings.impulse(connection.to, propagatedImpulse);
queue.push(connection.to);
}
}
});
// Continue propagation
setTimeout(propagate, 50); // Small delay for visual effect
};
propagate();
}, [galileoSprings, connections]);
return {
...galileoSprings,
triggerChainReaction
};
}
// Hook for orbital mechanics
function useOrbitalMechanics(centralBody, orbitingBodies, config = {}) {
const {
gravitationalConstant = 100,
timeScale = 1,
...springConfig
} = config;
const allBodies = [centralBody, ...orbitingBodies];
const galileoSprings = useGalileoSprings(allBodies, springConfig);
const updateOrbits = useCallback(() => {
orbitingBodies.forEach(body => {
const orbitingBody = galileoSprings.getBody(body.id);
const central = galileoSprings.getBody(centralBody.id);
if (!orbitingBody || !central) return;
// Calculate gravitational force
const dx = central.position.x - orbitingBody.position.x;
const dy = central.position.y - orbitingBody.position.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
const force = gravitationalConstant * centralBody.mass * body.mass / (distance * distance);
const forceX = force * dx / distance;
const forceY = force * dy / distance;
// Apply gravitational force
galileoSprings.physicsSystem?.applyForce(body.id, {
x: forceX * timeScale,
y: forceY * timeScale
}, 1 / 60);
}
});
}, [galileoSprings, centralBody, orbitingBodies, gravitationalConstant, timeScale]);
useEffect(() => {
const interval = setInterval(updateOrbits, 1000 / 60); // 60 FPS
return () => clearInterval(interval);
}, [updateOrbits]);
return {
...galileoSprings,
centralBody: centralBody.id,
orbitingBodies: orbitingBodies.map(b => b.id),
updateOrbits
};
}
// Hook for particle systems
function useParticleSystem(emitterPosition, config = {}) {
const {
particleCount = 50,
emissionRate = 10,
particleLife = 3000,
initialVelocity = {
x: 0,
y: -100
},
spread = 30,
gravity = 9.81
} = config;
const particles = [];
const particleIds = [];
// Create particle bodies
for (let i = 0; i < particleCount; i++) {
const angle = (Math.random() - 0.5) * spread * Math.PI / 180;
const speed = Math.random() * 50 + 25;
particles.push({
id: `particle-${i}`,
position: {
...emitterPosition
},
velocity: {
x: initialVelocity.x + Math.cos(angle) * speed,
y: initialVelocity.y + Math.sin(angle) * speed
},
mass: 0.1,
radius: 2
});
particleIds.push(`particle-${i}`);
}
const galileoSprings = useGalileoSprings(particles, {
gravity: true,
gravityStrength: gravity,
enableCollisions: false
});
const emit = useCallback(() => {
// Reset all particles to emitter position
particleIds.forEach((id, index) => {
const angle = (Math.random() - 0.5) * spread * Math.PI / 180;
const speed = Math.random() * 50 + 25;
galileoSprings.setPosition(id, {
...emitterPosition
});
galileoSprings.impulse(id, {
x: initialVelocity.x + Math.cos(angle) * speed,
y: initialVelocity.y + Math.sin(angle) * speed
});
});
}, [galileoSprings, emitterPosition, spread, initialVelocity, particleIds]);
const updateEmitter = useCallback(newPosition => {
// This would update the emitter position for continuous emission
// For now, just update the reference
Object.assign(emitterPosition, newPosition);
}, [emitterPosition]);
return {
...galileoSprings,
particleIds,
emit,
updateEmitter
};
}
// Hook for soft body physics
function useSoftBodyPhysics(bodyDefinition, config = {}) {
const nodes = bodyDefinition.nodes.map(node => ({
id: node.id,
position: {
x: node.x,
y: node.y
},
mass: node.mass,
radius: 5
}));
const galileoSprings = useGalileoSprings(nodes, config);
const updateSprings = useCallback(() => {
bodyDefinition.springs.forEach(spring => {
const fromBody = galileoSprings.getBody(spring.from);
const toBody = galileoSprings.getBody(spring.to);
if (!fromBody || !toBody) return;
const dx = toBody.position.x - fromBody.position.x;
const dy = toBody.position.y - fromBody.position.y;
const distance = Math.sqrt(dx * dx + dy * dy);
const displacement = distance - spring.restLength;
if (Math.abs(displacement) > 1) {
const force = displacement * spring.stiffness;
const forceX = force * dx / distance;
const forceY = force * dy / distance;
galileoSprings.impulse(spring.from, {
x: forceX,
y: forceY
});
galileoSprings.impulse(spring.to, {
x: -forceX,
y: -forceY
});
}
});
}, [galileoSprings, bodyDefinition.springs]);
useEffect(() => {
const interval = setInterval(updateSprings, 1000 / 60);
return () => clearInterval(interval);
}, [updateSprings]);
return {
...galileoSprings,
springs: bodyDefinition.springs,
updateSprings
};
}
export { useChainReaction, useGalileoSprings, useOrbitalMechanics, useParticleSystem, useSoftBodyPhysics };
//# sourceMappingURL=useGalileoSprings.js.map