aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
315 lines (312 loc) • 9.15 kB
JavaScript
import { createVector2D, addVectors, multiplyVector, subtractVectors } from '../types/common.js';
/**
* AuraGlass Physics Engine
* A comprehensive 2D physics simulation system with collision detection,
* spring dynamics, and gesture-based interactions
*/
/**
* AuraGlass Physics Engine API
*/
class AuraPhysicsEngineAPI {
constructor() {
this.bodies = new Map();
this.running = false;
this.lastTime = 0;
this.animationFrameId = null;
this.gravity = createVector2D(0, 9.8);
this.timeScale = 1.0;
this.update = this.update.bind(this);
}
/**
* Create a new physics body
*/
createBody(id, options = {}) {
const defaultOptions = {
mass: options.mass ?? 1,
friction: options.friction ?? 0.3,
restitution: options.restitution ?? 0.5,
fixedRotation: options.fixedRotation ?? false,
gravityScale: options.gravityScale ?? 1,
damping: options.damping ?? 0.01,
angularDamping: options.angularDamping ?? 0.01,
initialPosition: options.initialPosition ?? createVector2D(0, 0),
initialVelocity: options.initialVelocity ?? createVector2D(0, 0),
bounds: options.bounds ?? {
min: createVector2D(-Infinity, -Infinity),
max: createVector2D(Infinity, Infinity)
}
};
const body = {
id,
state: {
position: {
...defaultOptions.initialPosition
},
velocity: {
...defaultOptions.initialVelocity
},
acceleration: createVector2D(0, 0),
rotation: 0,
angularVelocity: 0,
mass: defaultOptions.mass,
timestamp: Date.now()
},
options: defaultOptions,
forces: [],
collisionListeners: []
};
this.bodies.set(id, body);
return id;
}
/**
* Remove a physics body
*/
removeBody(id) {
this.bodies.delete(id);
}
/**
* Apply force to a body
*/
applyForce(id, force) {
const body = this.bodies.get(id);
if (body) {
body.forces.push(force);
}
}
/**
* Apply impulse (instant velocity change) to a body
*/
applyImpulse(id, impulse) {
const body = this.bodies.get(id);
if (body) {
body.state.velocity = addVectors(body.state.velocity, multiplyVector(impulse, 1 / body.state.mass));
}
}
/**
* Set velocity of a body
*/
setVelocity(id, velocity) {
const body = this.bodies.get(id);
if (body) {
body.state.velocity = {
...velocity
};
}
}
/**
* Set position of a body
*/
setPosition(id, position) {
const body = this.bodies.get(id);
if (body) {
body.state.position = {
...position
};
}
}
/**
* Get the current state of a body
*/
getBodyState(id) {
const body = this.bodies.get(id);
return body ? {
...body.state
} : null;
}
/**
* Add collision listener to a body
*/
onCollision(id, listener) {
const body = this.bodies.get(id);
if (body) {
body.collisionListeners.push(listener);
return () => {
const index = body.collisionListeners.indexOf(listener);
if (index !== -1) {
body.collisionListeners.splice(index, 1);
}
};
}
return () => {};
}
/**
* Set gravity
*/
setGravity(gravity) {
this.gravity = gravity;
}
/**
* Set time scale (slow motion / speed up)
*/
setTimeScale(scale) {
this.timeScale = Math.max(0, scale);
}
/**
* Start the physics simulation
*/
start() {
if (!this.running) {
this.running = true;
this.lastTime = performance.now();
this.update();
}
}
/**
* Stop the physics simulation
*/
stop() {
this.running = false;
if (this.animationFrameId !== null) {
cancelAnimationFrame(this.animationFrameId);
this.animationFrameId = null;
}
}
/**
* Force an update of the physics simulation
*/
forceUpdate() {
const currentTime = performance.now();
const deltaTime = (currentTime - this.lastTime) / 1000;
this.lastTime = currentTime;
this.step(deltaTime * this.timeScale);
}
/**
* Main update loop
*/
update() {
if (!this.running) return;
const currentTime = performance.now();
const deltaTime = (currentTime - this.lastTime) / 1000;
this.lastTime = currentTime;
this.step(deltaTime * this.timeScale);
this.animationFrameId = requestAnimationFrame(this.update);
}
/**
* Physics simulation step
*/
step(deltaTime) {
// Cap delta time to prevent instability
const dt = Math.min(deltaTime, 0.016); // Max 60 FPS equivalent
this.bodies.forEach(body => {
// Apply gravity
const gravityForce = multiplyVector(this.gravity, body.state.mass * body.options.gravityScale);
body.forces.push(gravityForce);
// Calculate net force
const netForce = body.forces.reduce((acc, force) => addVectors(acc, force), createVector2D(0, 0));
// Calculate acceleration (F = ma)
body.state.acceleration = multiplyVector(netForce, 1 / body.state.mass);
// Update velocity
body.state.velocity = addVectors(body.state.velocity, multiplyVector(body.state.acceleration, dt));
// Apply damping
body.state.velocity = multiplyVector(body.state.velocity, Math.pow(1 - body.options.damping, dt));
// Update position
body.state.position = addVectors(body.state.position, multiplyVector(body.state.velocity, dt));
// Apply bounds
if (body.options.bounds) {
const {
min,
max
} = body.options.bounds;
if (body.state.position.x < min.x) {
body.state.position.x = min.x;
body.state.velocity.x *= -body.options.restitution;
}
if (body.state.position.x > max.x) {
body.state.position.x = max.x;
body.state.velocity.x *= -body.options.restitution;
}
if (body.state.position.y < min.y) {
body.state.position.y = min.y;
body.state.velocity.y *= -body.options.restitution;
}
if (body.state.position.y > max.y) {
body.state.position.y = max.y;
body.state.velocity.y *= -body.options.restitution;
}
}
// Update rotation
if (!body.options.fixedRotation) {
body.state.rotation += body.state.angularVelocity * dt;
body.state.angularVelocity *= Math.pow(1 - body.options.angularDamping, dt);
}
// Update timestamp
body.state.timestamp = Date.now();
// Clear forces for next frame
body.forces = [];
});
// Detect collisions (simple AABB for now)
this.detectCollisions();
}
/**
* Simple collision detection
*/
detectCollisions() {
const bodyArray = Array.from(this.bodies.values());
for (let i = 0; i < bodyArray.length; i++) {
for (let j = i + 1; j < bodyArray.length; j++) {
const bodyA = bodyArray[i];
const bodyB = bodyArray[j];
// Simple distance-based collision
const distance = Math.sqrt(Math.pow(bodyA.state.position.x - bodyB.state.position.x, 2) + Math.pow(bodyA.state.position.y - bodyB.state.position.y, 2));
// Assume radius of 20 for simplicity
const collisionThreshold = 40;
if (distance < collisionThreshold) {
const collisionPoint = createVector2D((bodyA.state.position.x + bodyB.state.position.x) / 2, (bodyA.state.position.y + bodyB.state.position.y) / 2);
const normal = subtractVectors(bodyB.state.position, bodyA.state.position);
const normalMagnitude = Math.sqrt(normal.x * normal.x + normal.y * normal.y);
const normalizedNormal = normalMagnitude > 0 ? multiplyVector(normal, 1 / normalMagnitude) : createVector2D(0, 0);
const event = {
bodyA: bodyA.id,
bodyB: bodyB.id,
point: collisionPoint,
normal: normalizedNormal,
penetration: collisionThreshold - distance,
timestamp: Date.now()
};
// Notify listeners
bodyA.collisionListeners.forEach(listener => listener(event));
bodyB.collisionListeners.forEach(listener => listener(event));
}
}
}
}
/**
* Get all body IDs
*/
getBodies() {
return Array.from(this.bodies.keys());
}
/**
* Clear all bodies
*/
clear() {
this.bodies.clear();
}
}
// Global instance
let globalPhysicsEngine = null;
/**
* Get the global physics engine instance
*/
const getGlobalPhysicsEngine = () => {
if (!globalPhysicsEngine) {
globalPhysicsEngine = new AuraPhysicsEngineAPI();
}
return globalPhysicsEngine;
};
/**
* Force update the global physics engine
*/
const forcePhysicsEngineUpdate = () => {
const engine = getGlobalPhysicsEngine();
engine.forceUpdate();
};
/**
* Get physics body state from the global engine
*/
const getPhysicsBodyState = bodyId => {
const engine = getGlobalPhysicsEngine();
return engine.getBodyState(bodyId);
};
export { AuraPhysicsEngineAPI, forcePhysicsEngineUpdate, getGlobalPhysicsEngine, getPhysicsBodyState };
//# sourceMappingURL=AuraPhysicsEngine.js.map