awacke1's picture
Update app.py
ed6a27c verified
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()