New revamped version of the AnyMeal recipe management software

More than 15 years ago after leaving university I developed a GNU/Linux software called AnyMeal for managing a large amount of recipes. The software was based on the MealMaster file format which is a popular file format for sharing recipes.

The website of Episoft Systems, which provided the original MealMaster software for DOS, is not online any more. Fortunately a copy of the website is still available on the Internet Archive.

The AnyMeal software used the recode library to convert recipe text files to the UTF-8 encoding. A fast Flex parser then converted the MealMaster recipes to XML. An XSLT script was used to generate SQL commands for inserting the recipe into a MySQL database. For setting up the database and user account, the software had a step-by-step wizard. The KDE password manager was used to store the user credentials.

The recipe search was performed using a complicated search dialog with multiple tabs. The graphical user interface used multiple windows to display recipes and lists of search results. A recipe editor was implemented for modifying existing recipes or adding new ones.

However life happened and eventually I stopped maintaining the software. The software was developed using Qt3 and when Qt4 came out, there were breaking changes with list views and the software didn't work on new systems any more.

Now many years later it still looks like there is a space for a fast recipe management software which can handle a large amount of MealMaster recipes. So I decided to use my past experience and do a rewrite of the old software.

This time I used the Qt5 framework to implement the graphical user interface. Instead of the old list view items, Qt5 now uses an item model and a list view widget to view the model. Also Qt5 provides cross-platform support for printing which is a great feature for a recipe management software. Like that it is not necessary to have a computer in the kitchen any more ;)

Instead of MySQL, I used the SQLite3 embedded database. This greatly simplified the software because SQLite3 does not require the setup of user accounts and databases. Also the user does not have to run a database server in the background. Furthermore without user credentials there is no need for interfacing with the KDE password manager any more.

For the internal representation of recipes I decided to use a more conservative approach. For representing recipes I used a C++ class instead of XML data. Like that it is not necessary to depend on the Xalan XSLT processor and Xerces-C XML library any more.

I also changed the order of UTF-8 conversion and parsing of recipes. Now the recipes are first parsed with an Flex MealMaster parser and stored as C++ objects. The strings of the C++ object then get converted to UTF-8 using the recode library. Like that it is not necessary for the Flex parser to be UTF-8 aware any more thus simplifying the implementation.

The search was also simplified. A search can be performed on title, category, or ingredient. It is possible to narrow down the search result by using several searches one by one. I.e. it is not necessary any more to implement a complicated search dialog which builds a complex search query.

Back then I used CVS for version control. This time the version control was done using Git. AnyMeal is still hosted on SourceForge however the source repository is now on Github, Gitlab, and BitBucket (after all Git is a distributed version control system!). The main project page is on Github (which displays the content of the gh-pages branch of the repository).

After simplifying the software architecture like that, it was possible to implement the software in less than 100 hours. Most of the work of the rewrite was implementing a simpler Flex parser and reimplementing the recipe editor. In contrast to back then, I implemented the internals of the application using test-driven development (TDD). I used Google Test which is Google's C++ test framework. Also, unlike back then, now there is Travis CI which provides free automated testing for software libre.

Finally using MSYS and MinGW the software was also compiled for Microsoft Windows. The Nullsoft Scriptable Install System (NSIS) was used to create an installer.

I am planning to add a few more small features but I think the software is more or less ready to be used for cooking.

Let me know what you think in the comment section below.

Enjoy!

Update:

Article was submitted to Hacker News.

I also forgot to mention that I used Docker and debuild to create a package for Debian Sid.

Rigid body game physics 6

part 1 part 2 part 3 part 4 part 5 part 6

A realistic simulation requires the estimation of friction forces.

Friction Forces

Colliding as well as resting contacts cause constraint impulses as well as friction impulses. So far we have only estimated the constraint impulses of colliding and resting contacts. Constraint impulses are in the direction of the contact's normal vector. Friction impulses act tangential to the contact plane (orthogonal to the normal vector). The inequality constraint is J and b have two rows. The vectors t1 and t2 are chosen so that they are orthogonal to the normal vector n and orthogonal to each other. Friction forces try to reduce tangential velocities to zero. I.e. b is

The linear components of J are and the angular components of J are

As before λ is computed Here λ has two elements. λ1 and λ2 are scaled with the same positive factor if necessary so that the length of the 2D vector consisting of λ1 and λ2 is smaller than the normal impulse Pn multiplied with the friction constant μ. Basically the overall force vector needs to reside in the friction cone. In a similar fashion as for constraint impulses, friction impulses are subtracted, recomputed, and then added to the accumulated impulses P of the two objects, when iterating.

friction cone

If you have made it this far, you know how to build a small physics engine!

Any feedback, comments, and suggestions are welcome.

dropping box

Further reading

Rigid body game physics 5

part 1 part 2 part 3 part 4 part 5 part 6

In order to handle collisions and resting contacts, it is necessary to determine contact points. One can achieve this using the separating plane algorithm (also see Baraff).

Separating Planes Algorithm

The separating plane algorithm only applies to convex polyhedra. However non-convex objects can in many cases be approximated by a union of convex polyhedra. A separating plane is a plane such that all points of the polyhedra A and B lie on opposite sides of the plane. If two polyhedra are not intersecting, a separating plane can either be defined using one of the faces of one of the polyhedra or be constructed from one edge of A and one edge of B.

The distance d of a point x from the plane P(p,n) defined by the point p and the normal vector n is:

two polyhedra and separating plane

  1. In the case of using a face from polyhedron A, the point p is a corner of the face. The normal vector n is the normalized cross product of two edges of the face and pointing outward from the polyhedron. The point x of polyhedron B with the smallest distance d defines the distance of B to the face of A. If this distance is positive, the face of polyhedron A is a separating plane.

  2. In the case of using two edges, the point p is the corner of an edge from polyhedron A. The normal vector n is the positive or negative normalized cross product of the two edge vectors. Again the point x of polyhedron B with the smallest distance d defines the distance of B to the face of A.

From all the planes, the one with the largest distance d is selected. If the distance is zero or negative, the two polyhedra are in contact. One can place a plane in the "middle" of the contact by moving it by half the distance d, i.e.

In the next step all points from A and B, which have a distance less than ε to the plane, are selected. Two vectors u and v orthogonal to the normal n and orthogonal to each other are determined. The points are projected onto the plane and represented using the vectors u and v: The convex hull of the each of the two polygons is determined. Then the intersection of the resulting two 2D polygons is computed. Finally the convex hull of the intersection is taken. The points are projected back into 3D space. These are the contact points used for simulating the colliding and resting contacts.

Rigid body game physics 4

part 1 part 2 part 3 part 4 part 5 part 6

The following article is partially based on Hubert Eichner's article on inequality constraints.

Inequality Constraints for Colliding Contacts

At a colliding contact the relative speed of the two objects is negative. I.e. a contact is a colliding (and not a resting) contact if the normal speed at the contact point is below a small negative threshold. Colliding contacts have restitution, i.e. the collision is partially elastic and the two objects will separate after the collision. The colliding contacts and affected joints are handled using a special time step of zero duration. Again the inequality constraint is The linear and angular components of J are the same as for a resting contact. The correction term b however depends on the initial normal speed at the contact point vn and the restitution coefficient ε:

Sequential Impulses for Collisions

Again the contact and joint impulses are estimated iteratively. Note that the normal speed vn at each contact is determined beforehand and stays constant during the iterations.

  • determine vn for all colliding contacts
  • for each iteration
    • for each joint
      1. compute Jacobian J and correction vector b
      2. predict speed u
      3. compute λ
      4. compute the impulse P
      5. add P to accumulated impulses of the two objects
    • for each colliding contact
      1. subtract old impulses P from previous iteration from accumulated impulses of the two objects
      2. compute Jacobian J and correction vector b
      3. predict speed u
      4. compute new λ and clip it to be greater or equal to zero
      5. compute the impulse P
      6. add P to accumulated impulses of the two objects
  • apply impulses to objects

The value λ is stored as Pn for limiting friction impulses lateron. The time step here is zero! Therefore external forces do not need to be considered while handling collisions. An object falling to the floor will experience several collisions until the linear and angular speed has decreased sufficiently for the contacts to become resting contacts.

Collision

Rigid body game physics 3

part 1 part 2 part 3 part 4 part 5 part 6

The following article is based on Hubert Eichner's article on inequality constraints.

Inequality Constraints for Resting Contacts

Contact points (resting contacts) are represented as inequality constraints. In contrast to a joint, a resting contact can only create repellent forces and no attracting forces. Similar to a joint constraint, the resting contact is represented using a function C(y(t)). Here C is one-dimensional and it is basically the distance of the two objects at the contact point. The inequality constraint of the resting contact is with the matrix J having one row and twelve columns. Instead of anchor points, one uses vectors ri and rj from the center of each object to the contact point. Using the contact normal n the inequality constraint becomes: The rotational component can be simplified as shown below: Thus the linear components of J are And the angular components of J are The correction term depends on the distance d. I.e. if the distance is negative, a correction is required so that the objects do not penetrate each other any more.

Sequential Impulses (updated)

The impulses generated by contact constraints and joint constraints are accumulated in a loop. The (cumulative) λ obtained for contact constraint furthermore is clipped to be greater or equal to zero. Note that it has to be the cumulative value which is clipped. The algorithm for solving the joint and contact constraints becomes:

  • for each iteration
    • for each joint
      1. compute Jacobian J and correction vector b
      2. predict speed u
      3. compute λ
      4. compute the impulse P
      5. add P to accumulated impulses of the two objects
    • for each resting contact
      1. subtract old impulses P from previous iteration from accumulated impulses of the two objects
      2. compute Jacobian J and correction vector b
      3. predict speed u
      4. compute new λ and clip it to be greater or equal to zero
      5. compute the impulse P
      6. add P to accumulated impulses of the two objects
  • use impulses and external forces to perform Runge Kutta integration step

The sequential impulse iterations have to be performed four times when using the Runge-Kutta method. The value λ is stored as Pn for limiting friction impulses lateron.

Contact