Blooming Maze – A Mass–Spring Driven Maze Flower
By Yuyuan CaoDescription
This piece starts with a full-screen black-background white maze. As time passes, the maze’s lines gradually converge to the shape of a flower.
The flower is formed by
- Mass–spring motion of maze vertices, and
- Line thickness modulation near the flower region.
- By frame 119, the maze has a flower in the middle, while still remaining a maze everywhere.
Pipeline & Features
1. Image preprocessing (from A1)
In main.cpp the program loads flower_color_80.png with stb_image. Reused A1 image functions:
- desaturate(…) (from desaturate.cpp)
- rgb_to_gray(…) (from rgb_to_gray.cpp)
- rgb_to_hsv(…), hsv_to_rgb(…)
These are used to convert the flower image to grayscale / HSV and create a mask that marks which pixels belong to the flower vs. background. The mask is downsampled into a per-cell weight array cell_w indicating “how much this maze cell belongs to the flower”.
2. Procedural maze generation
In main.cpp I implement a DFS-based maze generator with a Cell struct and carve_maze(…). This creates a perfect 80×80 maze (one connected component, no cycles). Each cell stores four walls (N,E,S,W) used for later rendering.
3. Mass–spring grid (inspired by A8)
Instead of using the A8 helper functions, I write a simplified mass–spring system directly in main.cpp:
- Node struct with rest position (x0, y0), current position (x, y), and velocity (vx, vy).
- Nodes are arranged in a (rows+1) × (cols+1) grid covering the maze.
Springs:
- Horizontal and vertical structural springs between neighboring nodes.
- Anchor springs that pull each node back toward its rest position.
Flower attraction:
- I compute node_w from nearby cell_w.
- For nodes where node_w > 0, I add a force that pulls them toward a flower-center direction; the strength is:
attract_force = attract_factor node_w (target_pos - current_pos);
attract_factor increases smoothly from 0 to 1 over 120 frames (smoothstep), so the flower gradually emerges.
4. Dynamic maze line rendering
Each maze wall samples nearby cell_w to get a wall weight w_strength. When drawing a wall in draw_wall(…) in main.cpp:
- The wall’s endpoints are taken from deformed node positions (mass–spring output).
- Line thickness depends on w_strength and attract_factor:
const int MAX_EXTRA = 3; double extra = w_strength MAX_EXTRA attract_factor; int thickness = 1 + (int)std::round(extra);
- At the start, all walls are 1-pixel thick → normal maze.
- Near the end, walls inside the flower become much thicker and visually dominant.
5. Animation & Output
The main loop generates NUM_FRAMES images (frame_000.png … frame_119.png) using stb_image_write. After rendering all frames, the program automatically calls:
ffmpeg -y -framerate 30 -i frame_%03d.png -pix_fmt yuv420p piece.mp4
The final deliverable for the assignment is piece.mp4.
5. Acknowledgements
CSC317 Assignment 1 starter code
- Functions reused: desaturate, rgb_to_gray, rgb_to_hsv, hsv_to_rgb.
- These were originally provided as part of A1.
stb libraries
- stb_image.h and stb_image_write.h by Sean Barrett.
- Public domain / MIT; used for PNG loading and saving.
Flower source image
- flower_color_80.png is adapted from: https://helloartsy.com/simple-flower-drawing/
- The image was downscaled and used as a mask to control where the maze converges.
ffmpeg
- Used to assemble the PNG frames into piece.mp4.
- Project website: https://ffmpeg.org