Table of Contents
Backstory
In the last post I turned pooltool from a code-based simulation into a 3D-rendered interactive game. In this post I extend this direction of the project by transitioning from primitive cartoon graphics to something a bit more realistic.
Sitting down to write this post has made me realize just how far down the rabbit hole I am. When I first started pooltool, I never even expected it to be rendered in 3D, let alone with eye-catching graphics. Fast forward to now, and I’ve inadvertently produced a video game that’s almost ready for an alpha release. Honestly, I don’t even know how I ended up here, but it’s been a pleasure to let the project evolve so organically.
Before this post, pooltool was full of primitive, textureless, and ugly assets, and I wanted to change that.
I found assets online, but none of them fully suited my needs. I wanted full control, which meant I would have to learn some basic 3D modeling. And so began my journey into the amazing open source modeling software, Blender.
Cue
To start out, I decided that modeling a cue would be relatively easy because of its simple geometry. After countless youtube tutorials, I ended up with the following model.
Then, at the cost of some casualties, I managed to get it into the scene.
Table
Taking a big leap in complexity, I started a several week process of trying to model a pool table, which involved many iterations.
As a practice round, I just tried to freestyle something that looked like a pool table, without trying to adhere to specific shapes and dimensions. What I ended up with was not half bad in my opinion.
With that under my belt, I iterated the process, this time with physically accurate measurements. I thought it would be most fun to use my own pool table as a reference, so I took about 30 reference photos. To get an idea, here is a few of them.
The next day I had an 8-hour flight, which I spent every minute of modeling. By the end of the flight, I had a physically accurate representation of my pool table inside pooltool.
Soon after that I figured out how to apply some UV textures.
At this point, it should be kept in mind that the simulation is completely disjoint from the model. For example, the simulation has its own set of parameters that define the cushion geometry, and I’m rendering this geometry using simple grey lines.
As you can tell, they don’t line up that well with the model. To make sure they do, I created a INI formatted file that specifies all of the model’s geometry in terms of the parameters used in the simulation. It looks like this:
[7_foot]
type = pocket
model = models/table/7_foot.glb
table_length = 1.9812
table_width = 0.9906
table_height = 0.708
cushion_width = 0.0508
cushion_height = 0.036576
corner_pocket_width = 0.118
corner_pocket_angle = 5.3
corner_pocket_depth = 0.0398
corner_pocket_radius = 0.0612
corner_jaw_radius = 0.02095
side_pocket_width = 0.137
side_pocket_angle = 7.14
side_pocket_depth = 0.00437
side_pocket_radius = 0.0645
side_jaw_radius = 0.00795
These parameters define the exact cushion geometry for the model. If I add more models in the future, I would simply add their geometrical parameters to this file and pooltool would be able to create the simulation geometry from these parameters.
Now, the simulation geometry matches the model geometry nearly perfectly.
Room
I wanted pooltool to be an immersive experience, so I decided to create a room. I saved this for last because it is the most involved. Thanks to Kourtney’s interior design skills, I think it turned out really well.
Here is an in-game screenshot of the room when it was about 75% finished.
And here is an interactive model of it fully completed:
If you look around, you should be able to spot Dexter.
This is actually a super realistic pencil crayon drawing we have in our apartment. The artist is Renaissance Pup. All of the other art in the room is in the public domain.
Balls
Then I turned my attention to the pool balls, which thus far have been black blobs.
To give my game a unique feel, I designed my own ball set. The strategy is to first design an image for each ball that will eventually get wrapped onto a sphere. This is called a UV image and here is the collection of UV images I created.
If you’re curious, the font is ITF Devanagari Marathi. Using Blender, these images can be wrapped around spheres which is how all of the ball models are made. For example, here is the 10-ball.
Eventually I’ll have to model more balls for games like 3-cushion. But for now this is good.
Want these files? SVG, PNG, BLEND, and GLB file formats for all of these balls can be found here
Optimizing
After adding the balls in, this was the state of things:
It looks good, but I was starting to think the table textures looked kind of bad. So I went to ambientCG, a database of public domain physical-based rendering (PBR) materials, and replaced the wood and cloth with textures I thought looked more realistic.
Then I did the same thing with the floors. Also also replaced one of the walls with brick, and added some trim so the contrast between floor and wall wasn’t so stark.
I also added some lights under the bar to add a nice ambience.
Performance
With PBR materials, lights, and shadows, performance is starting to become an issue on my poor macbook. So I created a little INI file config/settings
(introduced in this commit) that can be used to toggle various graphical settings. Right now you have the option to toggle the room, the floor, lights, shadows, shaders, whether PBR materials are used, and the target FPS.
As much as a like the high quality graphics I’ve been illustrating, I also think the following settings create a great cartoony vibe:
[graphics]
room = 0
floor = 0
lights = 0
shadows = 0
shader = 1
physical_based_rendering = 0
fps = 30
Cue avoidance
Once the graphics started to look quite realistic, I found it no longer acceptable for the cue to intersect geometry:
This unrealistic behavior is in stark contrast to the graphics.
To fix this, I created a routine that runs every frame and calculates the minimum cue elevation required to avoid intersecting any rails or balls. If the cue elevation is less than this, the cue is raised. While all of the collision detection is handled by built-in Panda3D capabilities, all of the collision resolution was handled by my own equations, which almost caused me to go insane, as evidenced by my notebook during this 3 day period.
The reason why I found this so difficult is because the cue stick doesn’t always aim at the center of the cue ball, which introduces several degrees of freedom. Nevertheless, I made it through, and the result looks good and feels intuitive.
All of the changes required for this feature can be found in this pull request.
Gallery
All in all, I think the game looks great now. Far better than I ever had expectations for. Here are some screenshots I’ve taken.
Next up
While pooltool is looking good, the physics engine needs some work. So next up on the agenda is increasing the physical realism.