From a failed 2D pile to a 3D simulator that can reject its own results
I started this because a friend had spent a full week at UCF helping run an angle-of-repose experiment. The physical work was much bigger than just pouring sand: the team needed a built apparatus, fabricated parts, repeated cleaning and resetting between trials, travel, and separate people handling the experiment and slope-analysis software. I wanted to know whether a digital twin could make that kind of research faster and easier to reproduce.
My first version was a 2D particle solver. It could draw a convincing pile, but the pre-validation checks showed that the biggest remaining problem was the physics model itself—not the line used to measure the slope. Fixing the graph would only have hidden the real problem, so I restarted the core in true 3D with PyBullet.
My notebook says the first material went from more than 12% error to under 5%, which felt huge. The other materials were not magically fixed: several quartz sizes were still too steep, and the glass-bead conditions revealed bad symmetry and input problems. I corrected the physical sizes, contact behavior, and measurement rules rather than entering the UCF answers and tuning until they matched.
Then the most important moment happened. I wrote “A breakthrough has occurred” across the page because the pre-validator saw that a run was likely to create bad data and stopped the simulation before it could report an angle. That is now the main idea of the project: the simulator should not just output a number—it should explain whether that number passed its own reliability checks.
The next bugs were more subtle. A few particles could roll away from the main pile and pull the measured slope downward, so I changed the analysis to isolate the supported pile body and compare two perpendicular views. The automatic experiment also trapped the user after it started, saved trials without a clear replay path, and showed technical cache/lock messages that made sense to the code but not to a new user.
I redesigned the interface around one action: click Run Current Trial, let the complete trial measure and save itself, review the result or replay, then move to the next trial. Replays now have familiar previous, play, pause, restart, next, and timeline controls, plus one shared speed setting. The diagnostic status is written in normal language and the controls are centered instead of scattered around the screen.
The final experiment contains 18 trials across unsieved quartz, three sieved quartz size ranges, and one- and two-cup glass-bead conditions. It stores the seeds, settings, raw XYZ positions, browser replays, CSV data, validation report, graphs, and final research report. Compared with the selected published benchmarks, the simulation was closer in four of six conditions and the UCF measurements were closer in two. The two-cup glass-bead case still has a large error, and I kept it because removing the worst result would defeat the purpose of the project.
I spent about 15 hours on the complete process. Hackatime captures about 4 hours from the project-specific coding records; the rest includes research, long-running simulations, reviewing results, handwritten planning, and work outside the tracked editor.
What I learned is that a believable visualization is not the same thing as a defensible experiment. The most valuable feature is not the 3D pile—it is the point where the program is willing to say, “This result is not ready to trust.”
Comments 0
No comments yet. Be the first!
Sign in to join the conversation.