Spaces:
Sleeping
Sleeping
import streamlit as st | |
def create_animation_app(): | |
st.title("Bouncing Yellow Balls & Jellyfish in a Rotating Sphere") | |
# We embed an HTML document that includes: | |
# 1. p5.js (from a CDN) | |
# 2. a container div for our canvas | |
# 3. our p5.js sketch that simulates 100 bouncing yellow balls with collision detection | |
# and one bouncing jellyfish (using a spiral drawing similar to your jellyfish code) | |
# all inside a sphere (drawn as a circle) that slowly rotates. | |
html_code = r""" | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="UTF-8"> | |
<!-- Include p5.js from a CDN --> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.min.js"></script> | |
<style> | |
/* Remove default margins & center the canvas */ | |
body { | |
margin: 0; | |
padding: 0; | |
overflow: hidden; | |
background: black; | |
} | |
#p5-container { | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="p5-container"></div> | |
<script> | |
// --- Global Variables --- | |
let balls = []; | |
let jellyfish; | |
const sphereRadius = 300; // radius of the bounding sphere | |
let sphereCenter; | |
let rotationAngle = 0; | |
const numBalls = 100; | |
// --- p5.js Setup --- | |
function setup() { | |
// Create an 800x800 canvas and attach it to our container | |
let canvas = createCanvas(800, 800); | |
canvas.parent('p5-container'); | |
sphereCenter = createVector(width/2, height/2); | |
// Set color mode to HSB for our jellyfish drawing. | |
colorMode(HSB, 360, 100, 100, 1); | |
// Create 100 balls. Each ball is given a random position (inside the sphere) | |
// and a random velocity. | |
for (let i = 0; i < numBalls; i++) { | |
balls.push(new Ball()); | |
} | |
// Create one jellyfish object. (Its drawn “shape” is produced by the jellyA() and getJellyColor() functions below.) | |
jellyfish = new Jellyfish(); | |
} | |
// --- p5.js Draw Loop --- | |
function draw() { | |
// Create a fade/trail effect by drawing a semi-transparent black background. | |
// (Note: using HSB mode so black is 0 saturation and 0 brightness.) | |
background(0, 0, 0, 0.1); | |
// Increment our rotation angle very slowly. | |
rotationAngle += 0.005; | |
// All drawing and physics is done in a coordinate system with origin at sphereCenter. | |
push(); | |
translate(sphereCenter.x, sphereCenter.y); | |
// Slowly rotate the entire coordinate system. | |
rotate(rotationAngle); | |
// Draw the bounding sphere (a circle) | |
noFill(); | |
stroke(255); | |
strokeWeight(2); | |
ellipse(0, 0, sphereRadius * 2, sphereRadius * 2); | |
// --- Update Physics --- | |
// 1. Update positions for all balls and the jellyfish. | |
for (let ball of balls) { | |
ball.update(); | |
} | |
jellyfish.update(); | |
// 2. Check and resolve ball-ball collisions. | |
for (let i = 0; i < balls.length; i++) { | |
for (let j = i + 1; j < balls.length; j++) { | |
balls[i].collide(balls[j]); | |
} | |
} | |
// 3. Check each ball for collisions with the sphere boundary and display them. | |
for (let ball of balls) { | |
ball.checkBoundaryCollision(); | |
ball.display(); | |
} | |
// 4. Check the jellyfish for sphere collisions and display it. | |
jellyfish.checkBoundaryCollision(); | |
jellyfish.display(); | |
pop(); | |
} | |
// --- Ball Class --- | |
class Ball { | |
constructor() { | |
this.r = 5; // radius of the ball | |
// Random position inside the sphere (by picking a random angle and radius) | |
let angle = random(TWO_PI); | |
let rad = random(sphereRadius - this.r); | |
this.pos = createVector(rad * cos(angle), rad * sin(angle)); | |
// Random speed and direction | |
let speed = random(1, 3); | |
let vAngle = random(TWO_PI); | |
this.vel = createVector(speed * cos(vAngle), speed * sin(vAngle)); | |
// Yellow color in HSB (hue=60, saturation=100, brightness=100) | |
this.col = color(60, 100, 100); | |
} | |
update() { | |
this.pos.add(this.vel); | |
} | |
// Check for collision with the circular (spherical) boundary. | |
checkBoundaryCollision() { | |
let d = this.pos.mag(); | |
if (d + this.r > sphereRadius) { | |
// Compute the normal (pointing outward) | |
let normal = this.pos.copy().normalize(); | |
// Reflect the velocity: v = v - 2*(v·normal)*normal | |
let dot = this.vel.dot(normal); | |
this.vel.sub(p5.Vector.mult(normal, 2 * dot)); | |
// Ensure the ball is repositioned just inside the boundary. | |
this.pos = normal.mult(sphereRadius - this.r); | |
} | |
} | |
// Check for collisions with another ball (elastic collision for equal masses) | |
collide(other) { | |
let diff = p5.Vector.sub(other.pos, this.pos); | |
let distBetween = diff.mag(); | |
if (distBetween < this.r + other.r) { | |
// Separate the overlapping balls by moving them apart equally. | |
let overlap = (this.r + other.r) - distBetween; | |
let displacement = diff.copy().normalize().mult(overlap / 2); | |
this.pos.sub(displacement); | |
other.pos.add(displacement); | |
// Compute collision response | |
let normal = diff.copy().normalize(); | |
let relativeVelocity = p5.Vector.sub(this.vel, other.vel); | |
let dotProd = relativeVelocity.dot(normal); | |
// Only resolve if balls are moving toward each other. | |
if (dotProd > 0) { | |
// For equal masses, exchange the velocity components along the collision normal. | |
let impulse = normal.copy().mult(dotProd); | |
this.vel.sub(impulse); | |
other.vel.add(impulse); | |
} | |
} | |
} | |
display() { | |
noStroke(); | |
fill(this.col); | |
ellipse(this.pos.x, this.pos.y, this.r * 2, this.r * 2); | |
} | |
} | |
// --- Jellyfish Class --- | |
class Jellyfish { | |
constructor() { | |
// For the jellyfish we choose a larger effective “radius” (for collision) of about 40. | |
this.size = 40; | |
// Start at a random position inside the sphere (with a margin) | |
this.pos = createVector(random(-sphereRadius + this.size, sphereRadius - this.size), | |
random(-sphereRadius + this.size, sphereRadius - this.size)); | |
// Give it a random velocity. | |
let speed = random(1, 2); | |
let angle = random(TWO_PI); | |
this.vel = createVector(speed * cos(angle), speed * sin(angle)); | |
// A time parameter used in the jellyfish drawing. | |
this.t = 0; | |
} | |
update() { | |
this.pos.add(this.vel); | |
this.t += 0.05; | |
} | |
// Bounce off the sphere boundary using a simple circular collision. | |
checkBoundaryCollision() { | |
if (this.pos.mag() + this.size > sphereRadius) { | |
let normal = this.pos.copy().normalize(); | |
let dot = this.vel.dot(normal); | |
this.vel.sub(p5.Vector.mult(normal, 2 * dot)); | |
this.pos = normal.mult(sphereRadius - this.size); | |
} | |
} | |
display() { | |
push(); | |
translate(this.pos.x, this.pos.y); | |
// Draw the jellyfish using a grid of points computed by the jellyA() function. | |
// (We subtract 200 from the computed positions so that the drawing is centered.) | |
strokeWeight(1.5); | |
for (let y = 99; y < 300; y += 4) { | |
for (let x = 99; x < 300; x += 2) { | |
let res = jellyA(x, y, this.t); | |
let px = res[0] - 200; | |
let py = res[1] - 200; | |
stroke(getJellyColor(x, y, this.t)); | |
point(px, py); | |
} | |
} | |
pop(); | |
} | |
} | |
// --- Jellyfish Drawing Functions --- | |
// Replicate the provided jellyfish “a(x,y)” function. | |
function jellyA(x, y, t) { | |
let k = x / 8 - 25; | |
let e = y / 8 - 25; | |
// d is computed as (k^2+e^2)/99 | |
let d = (k * k + e * e) / 99; | |
let q = x / 3 + k * 0.5 / cos(y * 5) * sin(d * d - t); | |
let c = d / 2 - t / 8; | |
let xPos = q * sin(c) + e * sin(d + k - t) + 200; | |
let yPos = (q + y / 8 + d * 9) * cos(c) + 200; | |
return [xPos, yPos]; | |
} | |
// Replicate the provided getColor function for the jellyfish. | |
function getJellyColor(x, y, t) { | |
let hue = (sin(t / 2) * 360 + x / 3 + y / 3) % 360; | |
let saturation = 70 + sin(t) * 30; | |
let brightness = 50 + cos(t / 2) * 20; | |
return color(hue, saturation, brightness, 0.5); | |
} | |
</script> | |
</body> | |
</html> | |
""" | |
# The height is set to 800px; adjust if needed. | |
st.components.v1.html(html_code, height=820, scrolling=False) | |
if __name__ == "__main__": | |
create_animation_app() | |