|
import streamlit as st |
|
|
|
def create_animation_app(): |
|
st.title("Bouncing Balls & Jellyfish in a Sphere") |
|
|
|
html_content = """ |
|
<style> |
|
.container { |
|
position: relative; |
|
width: 800px; |
|
height: 600px; |
|
margin: auto; |
|
} |
|
#animationCanvas, #p5Canvas { |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
} |
|
canvas { |
|
background-color: transparent; |
|
} |
|
</style> |
|
|
|
<div class="container"> |
|
<canvas id="animationCanvas"></canvas> |
|
<div id="p5Container"></div> |
|
</div> |
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script> |
|
|
|
<script> |
|
// Jellyfish Animation |
|
const jellyfishCanvas = document.getElementById('animationCanvas'); |
|
const ctx = jellyfishCanvas.getContext('2d'); |
|
jellyfishCanvas.width = 800; |
|
jellyfishCanvas.height = 600; |
|
|
|
let t = 0; |
|
|
|
function mag(x, y) { |
|
return Math.sqrt(x * x + y * y); |
|
} |
|
|
|
function a(x, y) { |
|
const k = x/8 - 25; |
|
const e = y/8 - 25; |
|
const d = mag(k, e)**2 / 99; |
|
|
|
const q = x/3 + k * 0.5 / Math.cos(y*5) * Math.sin(d*d - t); |
|
const c = d/2 - t/8; |
|
|
|
const xPos = q * Math.sin(c) + e * Math.sin(d + k - t) + 400; |
|
const yPos = (q + y/8 + d*9) * Math.cos(c) + 300; |
|
|
|
return [xPos, yPos]; |
|
} |
|
|
|
function getColor(x, y, t) { |
|
const hue = (Math.sin(t/2) * 360 + x/3 + y/3) % 360; |
|
const saturation = 70 + Math.sin(t) * 30; |
|
const lightness = 50 + Math.cos(t/2) * 20; |
|
return `hsla(${hue}, ${saturation}%, ${lightness}%, 0.5)`; |
|
} |
|
|
|
function drawJellyfish() { |
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)'; |
|
ctx.fillRect(0, 0, jellyfishCanvas.width, jellyfishCanvas.height); |
|
|
|
ctx.lineWidth = 1.5; |
|
|
|
for(let y = 99; y < 300; y += 4) { |
|
for(let x = 99; x < 300; x += 2) { |
|
const [px, py] = a(x, y); |
|
const color = getColor(x, y, t); |
|
|
|
ctx.strokeStyle = color; |
|
ctx.beginPath(); |
|
ctx.moveTo(px, py); |
|
ctx.lineTo(px + 1, py + 1); |
|
ctx.stroke(); |
|
} |
|
} |
|
|
|
t += Math.PI / 120; |
|
requestAnimationFrame(drawJellyfish); |
|
} |
|
|
|
// Initialize jellyfish animation |
|
drawJellyfish(); |
|
|
|
// Bouncing Balls Animation with p5.js |
|
new p5((sketch) => { |
|
let balls = []; |
|
const numBalls = 100; |
|
const sphereRadius = 200; |
|
let rotation = {x: 0, y: 0}; |
|
|
|
class Ball { |
|
constructor() { |
|
this.pos = p5.Vector.random3D().mult(sphereRadius * 0.8); |
|
this.vel = p5.Vector.random3D().mult(sketch.random(1, 3)); |
|
this.radius = 3; |
|
} |
|
|
|
update() { |
|
// Update position |
|
this.pos.add(this.vel); |
|
|
|
// Sphere collision |
|
let distance = this.pos.mag(); |
|
if (distance > sphereRadius - this.radius) { |
|
let normal = this.pos.copy().normalize(); |
|
let reflection = this.vel.copy().reflect(normal); |
|
this.vel.set(reflection); |
|
this.pos = normal.mult(sphereRadius - this.radius); |
|
} |
|
} |
|
|
|
display() { |
|
sketch.push(); |
|
sketch.translate(this.pos.x, this.pos.y, this.pos.z); |
|
sketch.fill(255, 255, 0); |
|
sketch.noStroke(); |
|
sketch.sphere(this.radius); |
|
sketch.pop(); |
|
} |
|
} |
|
|
|
function checkCollisions() { |
|
for(let i = 0; i < balls.length; i++) { |
|
for(let j = i+1; j < balls.length; j++) { |
|
let b1 = balls[i]; |
|
let b2 = balls[j]; |
|
let d = b1.pos.dist(b2.pos); |
|
let minDist = b1.radius + b2.radius; |
|
|
|
if (d < minDist) { |
|
let normal = p5.Vector.sub(b1.pos, b2.pos).normalize(); |
|
let overlap = (minDist - d) / 2; |
|
|
|
// Position correction |
|
b1.pos.add(p5.Vector.mult(normal, overlap)); |
|
b2.pos.sub(p5.Vector.mult(normal, overlap)); |
|
|
|
// Velocity adjustment |
|
let velDiff = p5.Vector.sub(b1.vel, b2.vel); |
|
let impulse = velDiff.dot(normal) / (1 + 1); |
|
b1.vel.sub(p5.Vector.mult(normal, impulse)); |
|
b2.vel.add(p5.Vector.mult(normal, impulse)); |
|
} |
|
} |
|
} |
|
} |
|
|
|
sketch.setup = () => { |
|
const p5Canvas = sketch.createCanvas(800, 600, sketch.WEBGL); |
|
p5Canvas.parent('p5Container'); |
|
sketch.frameRate(30); |
|
|
|
// Create balls |
|
for(let i = 0; i < numBalls; i++) { |
|
balls.push(new Ball()); |
|
} |
|
}; |
|
|
|
sketch.draw = () => { |
|
sketch.background(0, 0); |
|
sketch.rotateX(rotation.x); |
|
sketch.rotateY(rotation.y); |
|
|
|
// Slowly rotate the sphere |
|
rotation.x += 0.002; |
|
rotation.y += 0.003; |
|
|
|
// Draw wireframe sphere |
|
sketch.push(); |
|
sketch.noFill(); |
|
sketch.stroke(255, 50); |
|
sketch.sphere(sphereRadius); |
|
sketch.pop(); |
|
|
|
// Update and display balls |
|
balls.forEach(ball => { |
|
ball.update(); |
|
ball.display(); |
|
}); |
|
|
|
checkCollisions(); |
|
}; |
|
}, 'p5Container'); |
|
</script> |
|
""" |
|
|
|
st.components.v1.html(html_content, height=600) |
|
|
|
if __name__ == "__main__": |
|
create_animation_app() |