Update app.py
Browse files
app.py
CHANGED
@@ -1,257 +1,197 @@
|
|
1 |
import streamlit as st
|
2 |
|
3 |
def create_animation_app():
|
4 |
-
st.title("Bouncing
|
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 |
-
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 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
114 |
constructor() {
|
115 |
-
|
116 |
-
|
117 |
-
|
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 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
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 |
-
|
173 |
-
|
174 |
-
|
|
|
|
|
|
|
175 |
}
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
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 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
//
|
234 |
-
let
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
252 |
"""
|
253 |
-
|
254 |
-
st.components.v1.html(
|
255 |
|
256 |
if __name__ == "__main__":
|
257 |
-
create_animation_app()
|
|
|
1 |
import streamlit as st
|
2 |
|
3 |
def create_animation_app():
|
4 |
+
st.title("Bouncing Balls & Jellyfish in a Sphere")
|
5 |
+
|
6 |
+
html_content = """
|
7 |
+
<style>
|
8 |
+
.container {
|
9 |
+
position: relative;
|
10 |
+
width: 800px;
|
11 |
+
height: 600px;
|
12 |
+
margin: auto;
|
13 |
+
}
|
14 |
+
#animationCanvas, #p5Canvas {
|
15 |
+
position: absolute;
|
16 |
+
top: 0;
|
17 |
+
left: 0;
|
18 |
+
}
|
19 |
+
canvas {
|
20 |
+
background-color: transparent;
|
21 |
+
}
|
22 |
+
</style>
|
23 |
+
|
24 |
+
<div class="container">
|
25 |
+
<canvas id="animationCanvas"></canvas>
|
26 |
+
<div id="p5Container"></div>
|
27 |
+
</div>
|
28 |
+
|
29 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
|
30 |
+
|
31 |
+
<script>
|
32 |
+
// Jellyfish Animation
|
33 |
+
const jellyfishCanvas = document.getElementById('animationCanvas');
|
34 |
+
const ctx = jellyfishCanvas.getContext('2d');
|
35 |
+
jellyfishCanvas.width = 800;
|
36 |
+
jellyfishCanvas.height = 600;
|
37 |
+
|
38 |
+
let t = 0;
|
39 |
+
|
40 |
+
function mag(x, y) {
|
41 |
+
return Math.sqrt(x * x + y * y);
|
42 |
+
}
|
43 |
+
|
44 |
+
function a(x, y) {
|
45 |
+
const k = x/8 - 25;
|
46 |
+
const e = y/8 - 25;
|
47 |
+
const d = mag(k, e)**2 / 99;
|
48 |
+
|
49 |
+
const q = x/3 + k * 0.5 / Math.cos(y*5) * Math.sin(d*d - t);
|
50 |
+
const c = d/2 - t/8;
|
51 |
+
|
52 |
+
const xPos = q * Math.sin(c) + e * Math.sin(d + k - t) + 400;
|
53 |
+
const yPos = (q + y/8 + d*9) * Math.cos(c) + 300;
|
54 |
+
|
55 |
+
return [xPos, yPos];
|
56 |
+
}
|
57 |
+
|
58 |
+
function getColor(x, y, t) {
|
59 |
+
const hue = (Math.sin(t/2) * 360 + x/3 + y/3) % 360;
|
60 |
+
const saturation = 70 + Math.sin(t) * 30;
|
61 |
+
const lightness = 50 + Math.cos(t/2) * 20;
|
62 |
+
return `hsla(${hue}, ${saturation}%, ${lightness}%, 0.5)`;
|
63 |
+
}
|
64 |
+
|
65 |
+
function drawJellyfish() {
|
66 |
+
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
|
67 |
+
ctx.fillRect(0, 0, jellyfishCanvas.width, jellyfishCanvas.height);
|
68 |
+
|
69 |
+
ctx.lineWidth = 1.5;
|
70 |
+
|
71 |
+
for(let y = 99; y < 300; y += 4) {
|
72 |
+
for(let x = 99; x < 300; x += 2) {
|
73 |
+
const [px, py] = a(x, y);
|
74 |
+
const color = getColor(x, y, t);
|
75 |
+
|
76 |
+
ctx.strokeStyle = color;
|
77 |
+
ctx.beginPath();
|
78 |
+
ctx.moveTo(px, py);
|
79 |
+
ctx.lineTo(px + 1, py + 1);
|
80 |
+
ctx.stroke();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
81 |
}
|
82 |
+
}
|
83 |
+
|
84 |
+
t += Math.PI / 120;
|
85 |
+
requestAnimationFrame(drawJellyfish);
|
86 |
+
}
|
87 |
+
|
88 |
+
// Initialize jellyfish animation
|
89 |
+
drawJellyfish();
|
90 |
+
|
91 |
+
// Bouncing Balls Animation with p5.js
|
92 |
+
new p5((sketch) => {
|
93 |
+
let balls = [];
|
94 |
+
const numBalls = 100;
|
95 |
+
const sphereRadius = 200;
|
96 |
+
let rotation = {x: 0, y: 0};
|
97 |
+
|
98 |
+
class Ball {
|
99 |
constructor() {
|
100 |
+
this.pos = p5.Vector.random3D().mult(sphereRadius * 0.8);
|
101 |
+
this.vel = p5.Vector.random3D().mult(sketch.random(1, 3));
|
102 |
+
this.radius = 3;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
103 |
}
|
104 |
+
|
105 |
update() {
|
106 |
+
// Update position
|
107 |
+
this.pos.add(this.vel);
|
108 |
+
|
109 |
+
// Sphere collision
|
110 |
+
let distance = this.pos.mag();
|
111 |
+
if (distance > sphereRadius - this.radius) {
|
112 |
+
let normal = this.pos.copy().normalize();
|
113 |
+
let reflection = this.vel.copy().reflect(normal);
|
114 |
+
this.vel.set(reflection);
|
115 |
+
this.pos = normal.mult(sphereRadius - this.radius);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
116 |
}
|
|
|
117 |
}
|
118 |
+
|
119 |
display() {
|
120 |
+
sketch.push();
|
121 |
+
sketch.translate(this.pos.x, this.pos.y, this.pos.z);
|
122 |
+
sketch.fill(255, 255, 0);
|
123 |
+
sketch.noStroke();
|
124 |
+
sketch.sphere(this.radius);
|
125 |
+
sketch.pop();
|
126 |
}
|
127 |
+
}
|
128 |
+
|
129 |
+
function checkCollisions() {
|
130 |
+
for(let i = 0; i < balls.length; i++) {
|
131 |
+
for(let j = i+1; j < balls.length; j++) {
|
132 |
+
let b1 = balls[i];
|
133 |
+
let b2 = balls[j];
|
134 |
+
let d = b1.pos.dist(b2.pos);
|
135 |
+
let minDist = b1.radius + b2.radius;
|
136 |
+
|
137 |
+
if (d < minDist) {
|
138 |
+
let normal = p5.Vector.sub(b1.pos, b2.pos).normalize();
|
139 |
+
let overlap = (minDist - d) / 2;
|
140 |
+
|
141 |
+
// Position correction
|
142 |
+
b1.pos.add(p5.Vector.mult(normal, overlap));
|
143 |
+
b2.pos.sub(p5.Vector.mult(normal, overlap));
|
144 |
+
|
145 |
+
// Velocity adjustment
|
146 |
+
let velDiff = p5.Vector.sub(b1.vel, b2.vel);
|
147 |
+
let impulse = velDiff.dot(normal) / (1 + 1);
|
148 |
+
b1.vel.sub(p5.Vector.mult(normal, impulse));
|
149 |
+
b2.vel.add(p5.Vector.mult(normal, impulse));
|
150 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
151 |
}
|
|
|
|
|
152 |
}
|
153 |
+
}
|
154 |
+
|
155 |
+
sketch.setup = () => {
|
156 |
+
const p5Canvas = sketch.createCanvas(800, 600, sketch.WEBGL);
|
157 |
+
p5Canvas.parent('p5Container');
|
158 |
+
sketch.frameRate(30);
|
159 |
+
|
160 |
+
// Create balls
|
161 |
+
for(let i = 0; i < numBalls; i++) {
|
162 |
+
balls.push(new Ball());
|
163 |
+
}
|
164 |
+
};
|
165 |
+
|
166 |
+
sketch.draw = () => {
|
167 |
+
sketch.background(0, 0);
|
168 |
+
sketch.rotateX(rotation.x);
|
169 |
+
sketch.rotateY(rotation.y);
|
170 |
+
|
171 |
+
// Slowly rotate the sphere
|
172 |
+
rotation.x += 0.002;
|
173 |
+
rotation.y += 0.003;
|
174 |
+
|
175 |
+
// Draw wireframe sphere
|
176 |
+
sketch.push();
|
177 |
+
sketch.noFill();
|
178 |
+
sketch.stroke(255, 50);
|
179 |
+
sketch.sphere(sphereRadius);
|
180 |
+
sketch.pop();
|
181 |
+
|
182 |
+
// Update and display balls
|
183 |
+
balls.forEach(ball => {
|
184 |
+
ball.update();
|
185 |
+
ball.display();
|
186 |
+
});
|
187 |
+
|
188 |
+
checkCollisions();
|
189 |
+
};
|
190 |
+
}, 'p5Container');
|
191 |
+
</script>
|
192 |
"""
|
193 |
+
|
194 |
+
st.components.v1.html(html_content, height=600)
|
195 |
|
196 |
if __name__ == "__main__":
|
197 |
+
create_animation_app()
|