By default the OpenGL 3D normalized device coordinates (NDC) are between -1 and +1.
x is from left to right and y is from bottom to top of the screen while the z-axis is pointing into the screen.
Usually the 3D camera coordinates of the object are mapped such that near coordinates are mapped to -1 and far values are mapped to +1.
The near and far plane define the nearest and farthest point visualized by the rendering pipeline.
The absolute accuracy of floating point values is highest when the values are near zero.
This leads to the depth accuracy to be highest somewhere between the near and far plane which is suboptimal.
A better solution is to map near coordinates to 1 and far coordinates to 0.
In this case the higher precision near zero is used to compensate for the large range of distances covered towards the far plane.
Let x, y, z, 1 be the homogeneous coordinate of the 3D point and x’, y’, z’, w’ the homogeneous NDC.
Normally the 3D point uses a right-handed coordinate system where the z-axis points out of the screen.
This means that x and y are projected using negative z, i.e. w’ = -z.
The camera projection equations are x’/w’ = fw x/-z and y’/w’ = fh y/-z.
Substituting w’ = -z we get x’ = fw x and y’ = fh y.
Setting z’ = a + b z we get the following matrix equation:
When z = -near, we want z’/w’ = 1.
When z = -far, we want z’/w’ = 0.
For z = -near we get: z’ = w’ => a + b z = -z => a - b near = near.
For z = -far we get: z’ = 0 => a + b z = 0 => a - b far = 0 <=> b = a / far.
Substituting b we get: a - a near / far = near <=> a = near far / (far - near)
This means b = a / far = near / (far - near).
I.e. we get:
and
Finally we set fw = 1 / tan(fov / 2) where fov is the horizontal field of view angle (e.g. 60 degrees in radians).
fh then needs to be chosen such that fh / fw = w / h, where w and h are the width and height of the screen.
This is a summary on how to install Debian on a physical machine (as opposed to a VM).
I installed Debian 11 with KDE desktop on my HP 255 G3 notebook.
Because my computer has an AMD GPU it is recommended to download the unofficial Debian image with firmware.
The unofficial image also helps with Wireless adapters which frequently require proprietary firmware.
I downloaded the image which is 4 GByte in size and wrote it to a USB stick:
Replace /dev/sdb with your correct USB location! You can see the location in the log (run sudo dmesg) after plugging it in.
Under Windows you can use a tool such as Rufus to create a bootable USB stick.
I usually create a list of packages of my current system so that I can later install the same packages on the new system.
aptitude search '!~M ~i'-F'%p'> install.txt
I rebooted the computer.
My laptop uses UEFI secure boot so you need to press Escape and then F9 to get a list of bootable devices.
The Debian installer image on the USB stick should show up in the list. Select it and press return.
I proceeded to use the graphical installer.
The Debian installer can shrink Windows partitions if you want to set up dual boot.
Note that you need to disable Windows fast startup, otherwise your Windows file system will be in a hibernated state.
On my machine I created a swap partition about the same size as the computer memory.
I already have a 16 GByte root (/) ext4 partition (should be a bit bigger ideally) and a large home (/home) ext4 partition.
I just format the root partition and simply mount the home partition without formatting.
After setting up the users I can already continue using the system.
I noticed that even the unofficial image is missing some firmware.
So I cloned the Linux firmware repository and copied the firmware files:
git clone --depth 1 https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git
cd linux-firmware
sudo make install
Last week I published a minimal OpenGL example in Clojure.
The example was implemented using LWJGL version 2 (see LWJGL Wiki for documentation).
My motivation is to implement a space flight game, which requires rendering of planetary bodies.
I found an impressive free software called the Oreon Engine which is implemented in Java.
Watching the videos I realised that I should investigate tessellation shaders.
I didn’t find any small single-page code examples for tessellation shaders, so I decided to publish one here.
The example is implemented in Clojure but could be easily ported to C if you prefer.
The example uses several shaders required when doing tessellation using OpenGL:
a vertex shader
a tessellation control shader
a tessellation evaluation shader
a geometry shader
a fragment shader
The example not only shows how to set the tessellation level but it also shows how one can pass through texture coordinates.
The polygon mode was switched to display lines only so that one can observe how the triangle is split up.
Two years ago I published a minimal OpenGL example in C.
Recently I got renewed interest in the Clojure programming language
(if you want to learn Clojure, I can recommend the book Clojure in Action by the way).
I hope to do some game development in Clojure.
Initially I tried JOGL (Java Bindings for OpenGL), but then I switched to LWJGL version 2 (Lightweight Java Game Library).
There is a Debian package for LWJGL version 2 and there is extensive documentation on the LWJGL Wiki.
Using this library, I have translated my earlier example to Clojure.
See code below:
(nsraw-opengl(:import[org.lwjglBufferUtils][org.lwjgl.openglDisplayDisplayModeGL11GL12GL13GL15GL20GL30]))(defvertex-source"#version 130
in mediump vec3 point;
in mediump vec2 texcoord;
out mediump vec2 UV;
void main()
{
gl_Position = vec4(point, 1);
UV = texcoord;
}")(deffragment-source"#version 130
in mediump vec2 UV;
out mediump vec3 fragColor;
uniform sampler2D tex;
void main()
{
fragColor = texture(tex, UV).rgb;
}")(defvertices(float-array[0.50.50.01.01.0-0.50.50.00.01.0-0.5-0.50.00.00.0]))(defindices(int-array[012]))(defpixels(float-array[0.00.01.00.01.00.01.00.00.01.01.01.0]))(defnmake-shader[sourceshader-type](let[shader(GL20/glCreateShadershader-type)](GL20/glShaderSourceshadersource)(GL20/glCompileShadershader)(if(zero?(GL20/glGetShaderishaderGL20/GL_COMPILE_STATUS))(throw(Exception.(GL20/glGetShaderInfoLogshader1024))))shader))(defnmake-program[vertex-shaderfragment-shader](let[program(GL20/glCreateProgram)](GL20/glAttachShaderprogramvertex-shader)(GL20/glAttachShaderprogramfragment-shader)(GL20/glLinkProgramprogram)(if(zero?(GL20/glGetProgramiprogramGL20/GL_LINK_STATUS))(throw(Exception.(GL20/glGetProgramInfoLogprogram1024))))program))(defmacrodef-make-buffer[methodcreate-buffer]`(defn~method[data#](let[buffer#(~create-buffer(countdata#))](.putbuffer#data#)(.flipbuffer#)buffer#)))(def-make-buffermake-float-bufferBufferUtils/createFloatBuffer)(def-make-buffermake-int-bufferBufferUtils/createIntBuffer)(Display/setTitle"mini")(Display/setDisplayMode(DisplayMode.320240))(Display/create)(defvertex-shader(make-shadervertex-sourceGL20/GL_VERTEX_SHADER))(deffragment-shader(make-shaderfragment-sourceGL20/GL_FRAGMENT_SHADER))(defprogram(make-programvertex-shaderfragment-shader))(defvao(GL30/glGenVertexArrays))(GL30/glBindVertexArrayvao)(defvbo(GL15/glGenBuffers))(GL15/glBindBufferGL15/GL_ARRAY_BUFFERvbo)(defvertices-buffer(make-float-buffervertices))(GL15/glBufferDataGL15/GL_ARRAY_BUFFERvertices-bufferGL15/GL_STATIC_DRAW)(defidx(GL15/glGenBuffers))(GL15/glBindBufferGL15/GL_ELEMENT_ARRAY_BUFFERidx)(defindices-buffer(make-int-bufferindices))(GL15/glBufferDataGL15/GL_ELEMENT_ARRAY_BUFFERindices-bufferGL15/GL_STATIC_DRAW)(GL20/glVertexAttribPointer(GL20/glGetAttribLocationprogram"point")3GL11/GL_FLOATfalse(*5Float/BYTES)(*0Float/BYTES))(GL20/glVertexAttribPointer(GL20/glGetAttribLocationprogram"texcoord")2GL11/GL_FLOATfalse(*5Float/BYTES)(*3Float/BYTES))(GL20/glEnableVertexAttribArray0)(GL20/glEnableVertexAttribArray1)(GL20/glUseProgramprogram)(deftex(GL11/glGenTextures))(GL13/glActiveTextureGL13/GL_TEXTURE0)(GL11/glBindTextureGL11/GL_TEXTURE_2Dtex)(GL20/glUniform1i(GL20/glGetUniformLocationprogram"tex")0)(defpixel-buffer(make-float-bufferpixels))(GL11/glTexImage2DGL11/GL_TEXTURE_2D0GL11/GL_RGB220GL12/GL_BGRGL11/GL_FLOATpixel-buffer)(GL11/glTexParameteriGL11/GL_TEXTURE_2DGL11/GL_TEXTURE_WRAP_SGL11/GL_REPEAT)(GL11/glTexParameteriGL11/GL_TEXTURE_2DGL11/GL_TEXTURE_MIN_FILTERGL11/GL_NEAREST)(GL11/glTexParameteriGL11/GL_TEXTURE_2DGL11/GL_TEXTURE_MAG_FILTERGL11/GL_NEAREST)(GL30/glGenerateMipmapGL11/GL_TEXTURE_2D)(GL11/glEnableGL11/GL_DEPTH_TEST)(while(not(Display/isCloseRequested))(GL11/glClearColor0.00.00.00.0)(GL11/glClear(bit-orGL11/GL_COLOR_BUFFER_BITGL11/GL_DEPTH_BUFFER_BIT))(GL11/glDrawElementsGL11/GL_TRIANGLES3GL11/GL_UNSIGNED_INT0)(Display/update)(Thread/sleep40))(GL20/glDisableVertexAttribArray1)(GL20/glDisableVertexAttribArray0)(GL11/glBindTextureGL11/GL_TEXTURE_2D0)(GL11/glDeleteTexturestex)(GL15/glBindBufferGL15/GL_ELEMENT_ARRAY_BUFFER0)(GL15/glDeleteBuffersidx)(GL15/glBindBufferGL15/GL_ARRAY_BUFFER0)(GL15/glDeleteBuffersvbo)(GL30/glBindVertexArray0)(GL30/glDeleteVertexArraysvao)(GL20/glDetachShaderprogramvertex-shader)(GL20/glDetachShaderprogramfragment-shader)(GL20/glDeleteProgramprogram)(GL20/glDeleteShadervertex-shader)(GL20/glDeleteShaderfragment-shader)(Display/destroy)
The MealMaster file format (*.MMF or *.MM file) is a popular file format for sharing recipes.
MealMaster is the file format introduced by the MealMaster software developed by Episoft Systems.
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.
If you are looking for a MealMaster compatible recipe management software, please checkout my project AnyMeal!
One-column format
Below follows an example of the one-column MealMaster format (the ingredients are listed in a single column).
MMMMM----------------Meal-Master recipe exported by AnyMeal-----------------
Title: Fruit-Meringue-Pie
Categories: pastries,cakes
Servings: 1 pie
MMMMM----------------------------sponge mixture-----------------------------
150 g soft butter
150 g sugar
3 eggs
150 g flour
1 ts baking powder
MMMMM-------------------------------meringue--------------------------------
4 egg white
150 g sugar
50 g almond leaves
MMMMM-------------------------------filling---------------------------------
370 g sour cherries;dripped off (
-1 glass)
1 1/2 tb cornflour
1/4 l cherry juice
250 g cream
1 tb sugar
1 pk stabiliser
Butter spring form with butter paper. Preheat oven to 200°.
MMMMM----------------------------sponge mixture-----------------------------
Stir butter, sugar and eggs until foamy, mix flour and baking powder and stir
in. Fill dough in in two spring forms and smooth down. Pre-bake for 15
minutes.
MMMMM-------------------------------meringue--------------------------------
Beat egg white until stiff and let sugar trickle in spoon by spoon. Beat
until stiff. Spread meringue base on pre-backed flan bases. Scatter almond
leaves over them. Bake for further 10-15 minutes. Let flan bases cool down on
a grid.
MMMMM-------------------------------filling---------------------------------
For the filling mix cornflour with some cherry juice. Bring remaining juice
to boiling point, stir in cornflour and boil up. Mix in sour cherries, let
cool down a bit and spread over one flan base.
Beat cream with sugar and stabiliser until stiff, spread over filling.
Cut second flan base in 12 pieces and reassemble on top of the filling. Let
cool down well.
MMMMM
The recipe header begins with MMMMM or —–.
The header also has to contain the word “Meal-Master”.
The recipe content then starts with a title of up to 60 characters.
Then a list of categories follows.
Each category can have up to 11 characters.
Then servings amount and unit are specified.
In this recipe there are three ingredient sections.
An ingredient section starts with MMMMM or —–.
Ingredient amounts are specified in column 1 to 7.
Column 9 and 10 specify the unit token.
Starting from column 12 ingredient text of up to 28 characters follows.
If the ingredient text is longer than 28 characters, it is broken up using ingredient continuation lines.
Ingredient continuation lines begin with a “-“ in column 12.
Finally the instruction text follows.
The instruction text also can be split up into multiple sections starting with MMMMM or —–.
The recipe ends with MMMMM.
Two-column format
Here is an example of the two-column format.
The ingredients are listed in two columns.
---------- Recipe via Meal-Master (tm) v8.01
Title: Cannoli
Categories: Italian, Desserts
Yield: 16 servings
----------------------------------FILLING----------------------------------
1 1/2 c Whole-milk ricotta cheese; 1 1/2 c Milk chocolate;
- well drained - coarsely chopped
3 tb Sugar 1/4 c Pistachio nuts;
1 1/2 ts Cinnamon - coarsely chopped
-----------------------------------DOUGH-----------------------------------
1 c All-purpose flour - or dry white wine
1 tb Sugar 2 c Vegetable oil
1 tb Butter or lard Colored sprinkles
4 tb To 5 Tbl sweet Marsala wine
In a bowl, combine all the filling ingredients and mix well. Refreigerate,
covered, until ready to fill the cannoli shells.
To make the dough, place the flour in a bowl or food processor. Add the
butter or lard and sugar and mix with a fork, or pulse, until the mixture
resembles coarse meal. Slowly add the 1/4 cup of wine and shape the mixture
into a ball; add a little more wine if the dough appears too dry. It should
be soft but not sticky. Knead the dough on a floured surface until smooth,
about 10 minutes. Wrap the dough and refrigerate for 45 minutes.
Place the chilled dough on a floured work surface. Divide the dough in
half. Work with 1 piece of dough at a time; keep the remaining dough
refrigerated. Roll the dough out to a very thin long rectangle about 14
inches long and 3 inches wide, either by hand or using a pasta machine set
to the finest setting. Cut the dough into 3-inch squares. Place a cannoli
form diagnoally across 1 square. Roll the dough up around the form so the
points meet in the center. Seal the points with a little water. Continue
making cylinders until all the dough is used.
In an electric skillet, heat the vegetable oil to 375F. Fry the cannoli 3
or 4 at a time, turning them as they brown and blister, until golden brown
on all sides. Drain them on brown paper. When they are cool enough to
handle, carefully slide the cannoli off the forms.
To serve, use a long iced tea spoon or a pastry bag without a tip to fill
the cannoli with the ricotta cheese mixture. Dip the ends into colored
sprinkles, arrange them on a tray, and sprinkle confectioner's sugar over
the tops. Serve at once.
NOTE: If you prefer, you can fry the cannoli in a deep fryer. Be sure to
fill the cannoli just before serving - any sooner will make the shells
soggy.
This recipe from CIAO ITALIA by Mary Ann Esposito
-----
Here “Yield” is used instead of “Servings”.
Note that the order of ingredients in each section is column major.
In this example the second ingredient section has a second column starting with an ingredient continuation line.
I.e. it is continuing the last ingredient of the first column.
This example also shows instruction text being indented by two spaces.
Empty lines are used to create paragraphs.
The recipe ends with —–.
Not shown in these examples is to use an instruction line beginning with a “:” to force a line break.
Units
The following table shows the meaning of each of the unit tokens:
special tokens
x
"per serving" (quantity not to be scaled)
placeholder (no unit)
metric unit of volume
ml
milliliter (0.001 liter)
cl
centiliter (0.01 liter)
dl
deciliter (0.1 liter)
l
liter
metric unit of weight
mg
milligram (0.001 gram)
cg
centigram (0.01 gram)
dg
decigram (0.1 gram)
g
gram
kg
kilogram (1000 gram)
U.S. unit of volume
fl
fluid ounce (1/128 gallon or 28.4130625 milliliter)
pt
pint (1/8 gallon or 473.176473 milliliter)
qt
quart (1/4 gallon or 0.946352946 liter)
ga
gallon (3.785411784 liter)
U.S. unit of weight
oz
ounce (1/16 pound or 28.34952312 gram)
lb
pound (453.59237 gram)
unprecise unit of volume
dr
drop (1/64 teaspoon)
ds
dash (1/8 teaspoon)
pn
pinch (1/16 teaspoon)
ts
teaspoon (1/6 fluid ounce or 4.928921595 milliliter)
tb
tablespoon (1/2 fluid ounce or 14.7867647825 milliliter)
The grammar uses several rules from the ABNF standard:
/ is used to specify alternative rules.
VCHAR stands for any printable character (except whitespace)
DIGIT is a digit (one of 0,1,2,3,…,9).
SP is the space character.
CR means carriage return (0x0D).
LF is the line-feed character (0x0A).
*( VCHAR / SP ) means any number of printable and space characters or no characters at all.
1*4( DIGIT ) is used to specify one to four digits.
[ CR ] is an optional carriage return character.
5( ‘M’ ) is exactly 5 capital “M”-characters.
Conclusion
The MealMaster file format is quite difficult to parse.
Also there are a few problems with the file format:
It is not possible to protect a comma in a category-name from interpretation as a separator.
To prevent an occurrence of —–, : or MMMMM in the instructions-text from being interpreted as a special token, some recipe applications prepend all instruction lines with two space characters, which need to be ignored during import.
The original Mealmaster software will refuse to import recipes exceeding a certain size. Also overlong titles, categories, and servings will be cropped.
There are many recipes with ingredient-text longer than 28 characters. Overlong ingredient lines are making it more difficult to distinguish between the one- and two-column format.
The ending of the ingredient section is not defined properly.
Some recipe applications are relying on an empty line separating the ingredients from the instructions. On the other hand the original Mealmaster application allows empty lines within the ingredient-section.
There are recipe applications exporting MealMaster files containing categories with more than 11 characters.
I hope this description will be useful to you if you are trying to parse MealMaster recipes.
Please let me know any feedback in the comment section below.