Update app.py
Browse files
app.py
CHANGED
@@ -1,100 +1,257 @@
|
|
1 |
import streamlit as st
|
2 |
|
3 |
def create_animation_app():
|
4 |
-
st.title("
|
5 |
-
|
6 |
-
#
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
}
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
91 |
"""
|
92 |
-
|
93 |
-
|
94 |
-
html_content = css + js_code
|
95 |
-
|
96 |
-
# Display using st.components.v1.html
|
97 |
-
st.components.v1.html(html_content, height=2160)
|
98 |
|
99 |
if __name__ == "__main__":
|
100 |
-
create_animation_app()
|
|
|
1 |
import streamlit as st
|
2 |
|
3 |
def create_animation_app():
|
4 |
+
st.title("Bouncing Yellow Balls & Jellyfish in a Rotating Sphere")
|
5 |
+
# We embed an HTML document that includes:
|
6 |
+
# 1. p5.js (from a CDN)
|
7 |
+
# 2. a container div for our canvas
|
8 |
+
# 3. our p5.js sketch that simulates 100 bouncing yellow balls with collision detection
|
9 |
+
# and one bouncing jellyfish (using a spiral drawing similar to your jellyfish code)
|
10 |
+
# all inside a sphere (drawn as a circle) that slowly rotates.
|
11 |
+
html_code = r"""
|
12 |
+
<!DOCTYPE html>
|
13 |
+
<html>
|
14 |
+
<head>
|
15 |
+
<meta charset="UTF-8">
|
16 |
+
<!-- Include p5.js from a CDN -->
|
17 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.min.js"></script>
|
18 |
+
<style>
|
19 |
+
/* Remove default margins & center the canvas */
|
20 |
+
body {
|
21 |
+
margin: 0;
|
22 |
+
padding: 0;
|
23 |
+
overflow: hidden;
|
24 |
+
background: black;
|
25 |
+
}
|
26 |
+
#p5-container {
|
27 |
+
display: flex;
|
28 |
+
justify-content: center;
|
29 |
+
align-items: center;
|
30 |
+
}
|
31 |
+
</style>
|
32 |
+
</head>
|
33 |
+
<body>
|
34 |
+
<div id="p5-container"></div>
|
35 |
+
<script>
|
36 |
+
// --- Global Variables ---
|
37 |
+
let balls = [];
|
38 |
+
let jellyfish;
|
39 |
+
const sphereRadius = 300; // radius of the bounding sphere
|
40 |
+
let sphereCenter;
|
41 |
+
let rotationAngle = 0;
|
42 |
+
const numBalls = 100;
|
43 |
+
|
44 |
+
// --- p5.js Setup ---
|
45 |
+
function setup() {
|
46 |
+
// Create an 800x800 canvas and attach it to our container
|
47 |
+
let canvas = createCanvas(800, 800);
|
48 |
+
canvas.parent('p5-container');
|
49 |
+
sphereCenter = createVector(width/2, height/2);
|
50 |
+
|
51 |
+
// Set color mode to HSB for our jellyfish drawing.
|
52 |
+
colorMode(HSB, 360, 100, 100, 1);
|
53 |
+
|
54 |
+
// Create 100 balls. Each ball is given a random position (inside the sphere)
|
55 |
+
// and a random velocity.
|
56 |
+
for (let i = 0; i < numBalls; i++) {
|
57 |
+
balls.push(new Ball());
|
58 |
+
}
|
59 |
+
|
60 |
+
// Create one jellyfish object. (Its drawn “shape” is produced by the jellyA() and getJellyColor() functions below.)
|
61 |
+
jellyfish = new Jellyfish();
|
62 |
+
}
|
63 |
+
|
64 |
+
// --- p5.js Draw Loop ---
|
65 |
+
function draw() {
|
66 |
+
// Create a fade/trail effect by drawing a semi-transparent black background.
|
67 |
+
// (Note: using HSB mode so black is 0 saturation and 0 brightness.)
|
68 |
+
background(0, 0, 0, 0.1);
|
69 |
+
|
70 |
+
// Increment our rotation angle very slowly.
|
71 |
+
rotationAngle += 0.005;
|
72 |
+
|
73 |
+
// All drawing and physics is done in a coordinate system with origin at sphereCenter.
|
74 |
+
push();
|
75 |
+
translate(sphereCenter.x, sphereCenter.y);
|
76 |
+
// Slowly rotate the entire coordinate system.
|
77 |
+
rotate(rotationAngle);
|
78 |
+
|
79 |
+
// Draw the bounding sphere (a circle)
|
80 |
+
noFill();
|
81 |
+
stroke(255);
|
82 |
+
strokeWeight(2);
|
83 |
+
ellipse(0, 0, sphereRadius * 2, sphereRadius * 2);
|
84 |
+
|
85 |
+
// --- Update Physics ---
|
86 |
+
// 1. Update positions for all balls and the jellyfish.
|
87 |
+
for (let ball of balls) {
|
88 |
+
ball.update();
|
89 |
+
}
|
90 |
+
jellyfish.update();
|
91 |
+
|
92 |
+
// 2. Check and resolve ball-ball collisions.
|
93 |
+
for (let i = 0; i < balls.length; i++) {
|
94 |
+
for (let j = i + 1; j < balls.length; j++) {
|
95 |
+
balls[i].collide(balls[j]);
|
96 |
+
}
|
97 |
+
}
|
98 |
+
|
99 |
+
// 3. Check each ball for collisions with the sphere boundary and display them.
|
100 |
+
for (let ball of balls) {
|
101 |
+
ball.checkBoundaryCollision();
|
102 |
+
ball.display();
|
103 |
+
}
|
104 |
+
|
105 |
+
// 4. Check the jellyfish for sphere collisions and display it.
|
106 |
+
jellyfish.checkBoundaryCollision();
|
107 |
+
jellyfish.display();
|
108 |
+
|
109 |
+
pop();
|
110 |
+
}
|
111 |
+
|
112 |
+
// --- Ball Class ---
|
113 |
+
class Ball {
|
114 |
+
constructor() {
|
115 |
+
this.r = 5; // radius of the ball
|
116 |
+
// Random position inside the sphere (by picking a random angle and radius)
|
117 |
+
let angle = random(TWO_PI);
|
118 |
+
let rad = random(sphereRadius - this.r);
|
119 |
+
this.pos = createVector(rad * cos(angle), rad * sin(angle));
|
120 |
+
// Random speed and direction
|
121 |
+
let speed = random(1, 3);
|
122 |
+
let vAngle = random(TWO_PI);
|
123 |
+
this.vel = createVector(speed * cos(vAngle), speed * sin(vAngle));
|
124 |
+
// Yellow color in HSB (hue=60, saturation=100, brightness=100)
|
125 |
+
this.col = color(60, 100, 100);
|
126 |
+
}
|
127 |
+
|
128 |
+
update() {
|
129 |
+
this.pos.add(this.vel);
|
130 |
+
}
|
131 |
+
|
132 |
+
// Check for collision with the circular (spherical) boundary.
|
133 |
+
checkBoundaryCollision() {
|
134 |
+
let d = this.pos.mag();
|
135 |
+
if (d + this.r > sphereRadius) {
|
136 |
+
// Compute the normal (pointing outward)
|
137 |
+
let normal = this.pos.copy().normalize();
|
138 |
+
// Reflect the velocity: v = v - 2*(v·normal)*normal
|
139 |
+
let dot = this.vel.dot(normal);
|
140 |
+
this.vel.sub(p5.Vector.mult(normal, 2 * dot));
|
141 |
+
// Ensure the ball is repositioned just inside the boundary.
|
142 |
+
this.pos = normal.mult(sphereRadius - this.r);
|
143 |
+
}
|
144 |
+
}
|
145 |
+
|
146 |
+
// Check for collisions with another ball (elastic collision for equal masses)
|
147 |
+
collide(other) {
|
148 |
+
let diff = p5.Vector.sub(other.pos, this.pos);
|
149 |
+
let distBetween = diff.mag();
|
150 |
+
if (distBetween < this.r + other.r) {
|
151 |
+
// Separate the overlapping balls by moving them apart equally.
|
152 |
+
let overlap = (this.r + other.r) - distBetween;
|
153 |
+
let displacement = diff.copy().normalize().mult(overlap / 2);
|
154 |
+
this.pos.sub(displacement);
|
155 |
+
other.pos.add(displacement);
|
156 |
|
157 |
+
// Compute collision response
|
158 |
+
let normal = diff.copy().normalize();
|
159 |
+
let relativeVelocity = p5.Vector.sub(this.vel, other.vel);
|
160 |
+
let dotProd = relativeVelocity.dot(normal);
|
161 |
+
// Only resolve if balls are moving toward each other.
|
162 |
+
if (dotProd > 0) {
|
163 |
+
// For equal masses, exchange the velocity components along the collision normal.
|
164 |
+
let impulse = normal.copy().mult(dotProd);
|
165 |
+
this.vel.sub(impulse);
|
166 |
+
other.vel.add(impulse);
|
167 |
+
}
|
168 |
+
}
|
169 |
}
|
170 |
+
|
171 |
+
display() {
|
172 |
+
noStroke();
|
173 |
+
fill(this.col);
|
174 |
+
ellipse(this.pos.x, this.pos.y, this.r * 2, this.r * 2);
|
175 |
+
}
|
176 |
+
}
|
177 |
+
|
178 |
+
// --- Jellyfish Class ---
|
179 |
+
class Jellyfish {
|
180 |
+
constructor() {
|
181 |
+
// For the jellyfish we choose a larger effective “radius” (for collision) of about 40.
|
182 |
+
this.size = 40;
|
183 |
+
// Start at a random position inside the sphere (with a margin)
|
184 |
+
this.pos = createVector(random(-sphereRadius + this.size, sphereRadius - this.size),
|
185 |
+
random(-sphereRadius + this.size, sphereRadius - this.size));
|
186 |
+
// Give it a random velocity.
|
187 |
+
let speed = random(1, 2);
|
188 |
+
let angle = random(TWO_PI);
|
189 |
+
this.vel = createVector(speed * cos(angle), speed * sin(angle));
|
190 |
+
// A time parameter used in the jellyfish drawing.
|
191 |
+
this.t = 0;
|
192 |
+
}
|
193 |
+
|
194 |
+
update() {
|
195 |
+
this.pos.add(this.vel);
|
196 |
+
this.t += 0.05;
|
197 |
+
}
|
198 |
+
|
199 |
+
// Bounce off the sphere boundary using a simple circular collision.
|
200 |
+
checkBoundaryCollision() {
|
201 |
+
if (this.pos.mag() + this.size > sphereRadius) {
|
202 |
+
let normal = this.pos.copy().normalize();
|
203 |
+
let dot = this.vel.dot(normal);
|
204 |
+
this.vel.sub(p5.Vector.mult(normal, 2 * dot));
|
205 |
+
this.pos = normal.mult(sphereRadius - this.size);
|
206 |
+
}
|
207 |
+
}
|
208 |
+
|
209 |
+
display() {
|
210 |
+
push();
|
211 |
+
translate(this.pos.x, this.pos.y);
|
212 |
+
// Draw the jellyfish using a grid of points computed by the jellyA() function.
|
213 |
+
// (We subtract 200 from the computed positions so that the drawing is centered.)
|
214 |
+
strokeWeight(1.5);
|
215 |
+
for (let y = 99; y < 300; y += 4) {
|
216 |
+
for (let x = 99; x < 300; x += 2) {
|
217 |
+
let res = jellyA(x, y, this.t);
|
218 |
+
let px = res[0] - 200;
|
219 |
+
let py = res[1] - 200;
|
220 |
+
stroke(getJellyColor(x, y, this.t));
|
221 |
+
point(px, py);
|
222 |
+
}
|
223 |
+
}
|
224 |
+
pop();
|
225 |
+
}
|
226 |
+
}
|
227 |
+
|
228 |
+
// --- Jellyfish Drawing Functions ---
|
229 |
+
// Replicate the provided jellyfish “a(x,y)” function.
|
230 |
+
function jellyA(x, y, t) {
|
231 |
+
let k = x / 8 - 25;
|
232 |
+
let e = y / 8 - 25;
|
233 |
+
// d is computed as (k^2+e^2)/99
|
234 |
+
let d = (k * k + e * e) / 99;
|
235 |
+
let q = x / 3 + k * 0.5 / cos(y * 5) * sin(d * d - t);
|
236 |
+
let c = d / 2 - t / 8;
|
237 |
+
let xPos = q * sin(c) + e * sin(d + k - t) + 200;
|
238 |
+
let yPos = (q + y / 8 + d * 9) * cos(c) + 200;
|
239 |
+
return [xPos, yPos];
|
240 |
+
}
|
241 |
+
|
242 |
+
// Replicate the provided getColor function for the jellyfish.
|
243 |
+
function getJellyColor(x, y, t) {
|
244 |
+
let hue = (sin(t / 2) * 360 + x / 3 + y / 3) % 360;
|
245 |
+
let saturation = 70 + sin(t) * 30;
|
246 |
+
let brightness = 50 + cos(t / 2) * 20;
|
247 |
+
return color(hue, saturation, brightness, 0.5);
|
248 |
+
}
|
249 |
+
</script>
|
250 |
+
</body>
|
251 |
+
</html>
|
252 |
"""
|
253 |
+
# The height is set to 800px; adjust if needed.
|
254 |
+
st.components.v1.html(html_code, height=820, scrolling=False)
|
|
|
|
|
|
|
|
|
255 |
|
256 |
if __name__ == "__main__":
|
257 |
+
create_animation_app()
|