awacke1 commited on
Commit
91145e2
·
verified ·
1 Parent(s): ed6a27c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +181 -241
app.py CHANGED
@@ -1,257 +1,197 @@
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()
 
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()