larry77

second formulation

May 14th, 2026
25
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.61 KB | None | 0 0
  1. #!/usr/bin/env python3
  2.  
  3. ###############################################################################
  4. # Optimised MP4 renderer for the newer jellyfish-like formula
  5. #
  6. # Newer formula:
  7. #
  8. # a=(m,d=mag(k=9*cos(i/61),e=i/652-13)**2/89+1)=>point(
  9. # (q=79-e/2*sin(k)+k/d*(6+5*sin(sin(d*d+e/9-t+m))))*
  10. # sin(c=d/1.9+cos(t-d*3+m)/11-t/16+m)+200,
  11. # (q+40)*cos(c)+200
  12. # )
  13. #
  14. # t=0,draw=$=>{
  15. # t||createCanvas(w=400,w);
  16. # background(9).stroke(w,96);
  17. # for(t+=PI/45,i=2e4;i--;)a(i%2*3)
  18. # }
  19. #
  20. # This version:
  21. #
  22. # - uses the newer formula;
  23. # - updates one existing scatter object frame by frame;
  24. # - writes directly to MP4 using ffmpeg;
  25. # - avoids writing thousands of intermediate PNG files;
  26. # - precomputes immutable quantities outside compute_frame().
  27. ###############################################################################
  28.  
  29. import numpy as np
  30. import matplotlib.pyplot as plt
  31. from matplotlib.animation import FuncAnimation, FFMpegWriter
  32.  
  33. # =============================================================================
  34. # 1. Tunable parameters
  35. # =============================================================================
  36.  
  37. n_points = 20_000
  38.  
  39. # 540 * 4 frames at 25 fps gives:
  40. #
  41. # 2160 / 25 = 86.4 seconds
  42. n_frames = 540
  43.  
  44. fps = 25
  45.  
  46. # Mathematical canvas from the original p5.js code:
  47. #
  48. # createCanvas(w = 400, w)
  49. canvas_width = 400
  50. canvas_height = 400
  51.  
  52. # Rendered output size.
  53. render_width_px = 600
  54. render_height_px = 600
  55.  
  56. # The final pixel size is figsize_inches * dpi.
  57. dpi = 100
  58.  
  59. # Visual tuning.
  60. #
  61. # Matplotlib scatter "s" is marker area in points squared.
  62. point_size = 1.5
  63. point_alpha = 0.70
  64.  
  65. background_colour = "#090909"
  66.  
  67. output_file = "jellyfish_new_formula_python.mp4"
  68.  
  69. # FFmpeg settings.
  70. ffmpeg_crf = 18
  71. ffmpeg_preset = "veryfast"
  72. ffmpeg_threads = 4
  73.  
  74. # =============================================================================
  75. # 2. Static point data
  76. # =============================================================================
  77.  
  78. # Point index:
  79. #
  80. # i = 0, 1, ..., n_points - 1
  81. #
  82. # These are not x-coordinates. They are indices used to generate the point cloud.
  83. i = np.arange(n_points)
  84.  
  85. # New formula:
  86. #
  87. # k = 9 * cos(i / 61)
  88. k = 9 * np.cos(i / 61)
  89.  
  90. # New formula:
  91. #
  92. # e = i / 652 - 13
  93. e = i / 652 - 13
  94.  
  95. # New formula:
  96. #
  97. # d = mag(k, e)^2 / 89 + 1
  98. #
  99. # Since:
  100. #
  101. # mag(k, e)^2 = k^2 + e^2
  102. #
  103. # we compute it directly as:
  104. d = (k**2 + e**2) / 89 + 1
  105.  
  106. # New formula:
  107. #
  108. # m = i % 2 * 3
  109. #
  110. # This alternates between 0 and 3 and creates the two intertwined creatures.
  111. m = (i % 2) * 3
  112.  
  113. # =============================================================================
  114. # 3. Precomputed immutable terms
  115. # =============================================================================
  116.  
  117. # These do not depend on frame/time, so compute them once.
  118.  
  119. d2 = d**2
  120.  
  121. # Static part of q:
  122. #
  123. # 79 - e/2*sin(k)
  124. q_static = 79 - e / 2 * np.sin(k)
  125.  
  126. # Multiplier in q:
  127. #
  128. # k / d
  129. q_multiplier = k / d
  130.  
  131. # Static part inside the nested sine:
  132. #
  133. # d*d + e/9 - t + m
  134. #
  135. # Frame-dependent part is just "- t".
  136. wave_static = d2 + e / 9 + m
  137.  
  138. # Static part of c:
  139. #
  140. # d/1.9 + cos(t - d*3 + m)/11 - t/16 + m
  141. #
  142. # Split into:
  143. #
  144. # c_static = d/1.9 + m
  145. # cosine argument = t + (-d*3 + m)
  146. c_static = d / 1.9 + m
  147. c_wave_static = -d * 3 + m
  148.  
  149. # Preallocated coordinate array for Matplotlib scatter offsets.
  150. #
  151. # Matplotlib expects an N x 2 array:
  152. #
  153. # [[x1, y1],
  154. # [x2, y2],
  155. # ...]
  156. offsets = np.empty((n_points, 2), dtype=np.float64)
  157.  
  158. # =============================================================================
  159. # 4. Frame computation
  160. # =============================================================================
  161.  
  162. def compute_frame(frame_id):
  163. """
  164. Compute one animation frame.
  165.  
  166. Parameters
  167. ----------
  168. frame_id : int
  169. Integer frame number: 1, 2, 3, ...
  170.  
  171. It is converted to continuous time as:
  172.  
  173. t = frame_id * pi / 45
  174.  
  175. matching the original JavaScript update:
  176.  
  177. t += PI / 45
  178.  
  179. Returns
  180. -------
  181. offsets : numpy.ndarray
  182. N x 2 array with x/y coordinates for all points.
  183. """
  184.  
  185. # Frame time.
  186. t = frame_id * np.pi / 45
  187.  
  188. # -------------------------------------------------------------------------
  189. # Main deformation term q
  190. # -------------------------------------------------------------------------
  191. #
  192. # Original:
  193. #
  194. # q = 79 - e/2*sin(k) +
  195. # k/d * (6 + 5*sin(sin(d*d + e/9 - t + m)))
  196. #
  197. q = (
  198. q_static
  199. + q_multiplier
  200. * (6 + 5 * np.sin(np.sin(wave_static - t)))
  201. )
  202.  
  203. # -------------------------------------------------------------------------
  204. # Angular term c
  205. # -------------------------------------------------------------------------
  206. #
  207. # Original:
  208. #
  209. # c = d/1.9 + cos(t - d*3 + m)/11 - t/16 + m
  210. #
  211. c = (
  212. c_static
  213. + np.cos(t + c_wave_static) / 11
  214. - t / 16
  215. )
  216.  
  217. # -------------------------------------------------------------------------
  218. # Final coordinates
  219. # -------------------------------------------------------------------------
  220. #
  221. # Original:
  222. #
  223. # x = q * sin(c) + 200
  224. # y = (q + 40) * cos(c) + 200
  225. #
  226. offsets[:, 0] = q * np.sin(c) + 200
  227. offsets[:, 1] = (q + 40) * np.cos(c) + 200
  228.  
  229. return offsets
  230.  
  231. # =============================================================================
  232. # 5. Matplotlib figure setup
  233. # =============================================================================
  234.  
  235. fig_width_inches = render_width_px / dpi
  236. fig_height_inches = render_height_px / dpi
  237.  
  238. fig, ax = plt.subplots(
  239. figsize=(fig_width_inches, fig_height_inches),
  240. dpi=dpi
  241. )
  242.  
  243. fig.patch.set_facecolor(background_colour)
  244. ax.set_facecolor(background_colour)
  245.  
  246. ax.set_xlim(0, canvas_width)
  247. ax.set_ylim(0, canvas_height)
  248. ax.set_aspect("equal")
  249.  
  250. ax.set_xticks([])
  251. ax.set_yticks([])
  252.  
  253. for spine in ax.spines.values():
  254. spine.set_visible(False)
  255.  
  256. ax.margins(0)
  257. plt.subplots_adjust(left=0, right=1, top=1, bottom=0)
  258.  
  259. # =============================================================================
  260. # 6. Initial frame and scatter object
  261. # =============================================================================
  262.  
  263. initial_offsets = compute_frame(1).copy()
  264.  
  265. points = ax.scatter(
  266. initial_offsets[:, 0],
  267. initial_offsets[:, 1],
  268. s=point_size,
  269. c="white",
  270. alpha=point_alpha,
  271. linewidths=0
  272. )
  273.  
  274. # =============================================================================
  275. # 7. Animation update function
  276. # =============================================================================
  277.  
  278. def update(frame_id):
  279. current_offsets = compute_frame(frame_id)
  280.  
  281. # Update the existing scatter object.
  282. # This avoids rebuilding the plot for every frame.
  283. points.set_offsets(current_offsets)
  284.  
  285. return (points,)
  286.  
  287. # =============================================================================
  288. # 8. Build and save animation directly as MP4
  289. # =============================================================================
  290.  
  291. animation = FuncAnimation(
  292. fig,
  293. update,
  294. frames=np.arange(1, n_frames + 1),
  295. interval=1000 / fps,
  296. blit=True
  297. )
  298.  
  299. writer = FFMpegWriter(
  300. fps=fps,
  301. codec="libx264",
  302. bitrate=-1,
  303. extra_args=[
  304. "-preset", ffmpeg_preset,
  305. "-crf", str(ffmpeg_crf),
  306. "-threads", str(ffmpeg_threads),
  307. "-pix_fmt", "yuv420p",
  308. ],
  309. )
  310.  
  311. animation.save(
  312. output_file,
  313. writer=writer
  314. )
  315.  
  316. plt.close(fig)
  317.  
  318. print(f"Saved: {output_file}")
  319.  
Advertisement
Add Comment
Please, Sign In to add comment