One year ago, I wrote an article about the modelling of the egg shapes, promising at one point to come back on the topics. A next step in studying eggs shapes is to look to real one or a copy of it. A happy occasion for experimenting with the model using three-dimensional graphics and 3d Printing! That is a natural indeed step: take half of the symmetric curve representing the egg shape
,
where and
are two parameters, and rotate it around the central axis
Then using the OpenGL and GLUT graphical libraries, it is quite easy to come out with a simple program that does the job. I have added some screenshots of the program that youc an find in the appendix of the article. In the paper by Stoddard et al. [3], the asymmetry () and ellipticity (
) parameters are used instead to define the charateristic of the analyzed eggs. For
and
, the egg has a spherical shape, as in the figure below.

If the value of the parameter increase the egg shape become more elliptic. For example, with A=0 and E=1, we get the egg in the Figure 2.

If the value of the parameter increase the egg shape become more asymmetric. For example, with A=1 and E=0, we get the egg in the Figure 3.

Please refer to my previous article to have information about the connection of the above egg shapes example with those of real bird eggs.
If you liked this article do not forget to press the like button and share it!
BUONA PASQUA – HAPPY EASTER – FROHE OSTERN
APPENDIX
This is the source code in C++ of the program. It use OpenGl library and the GLUT library for the GUI. The code can be compiled under MacOS using the comand:
g++ eggshapes.cpp -framework opengl -framework cocoa -framework glut -Wno-deprecated -lglui
It should be easy to compile it under Linux, Android or Windows system as well.
/*
* PROGRAM NAME: Eggshapes
* VERSION : 1.0
* DESCRIPTION :
*
* THE BIRDS EGG SHAPE GENERATOR
*
* The program implement the Baker equation in 3D
* to generate shapes of eggs of different birds
*
*
* (c) Danilo Roccatano 2019-20
*
*/
#include <fstream>
#include <iostream>
#include <iomanip>
#include <math.h>
#include <GLUT/glut.h>
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#include <GL/glui.h>
#define DEBUG -1
using namespace std;
/** These are the live variables passed into GLUI ***/
float pA=0.5; // initial value variable T
float pE=0.5; // initial value variable lambda
int np=100 ; // Number of points for the egg shape
int nrot=100 ; // Number of rotation along the egg axis
int main_window;
GLint spin= 0;
GLint spin1=0;
GLint egg= 0;
GLUI_Spinner *spi[11];
//Viewer options (GluLookAt)
float fovy = 60.0, aspect = 1.0, zNear = 1.0, zFar = 100.0;
//Mouse modifiers
float depth = -1;
float scale=2.5;
float psi=0, theta=0;
float downX, downY;
bool leftButton = false, middleButton = false, rightButton=false;
int showAxis;
int showLight=1,showLight1=1;
int sqstripes=-1;
int chir;
int DrawEgg();
void mouse ();
void atome(GLfloat []);
void drawAxis();
void mouseCallback(int button, int state, int x, int y);
void motionCallback(int x, int y);
/***************************************** myGlutIdle() ***********/
void myGlutIdle( void )
{
/* According to the GLUT specification, the current window is
undefined during an idle callback. So we need to explicitly change
it if necessary */
if ( glutGetWindow() != main_window )
glutSetWindow(main_window);
glutPostRedisplay();
}
// ============ Some 3D math routines
//------ Returns the cross product of 2 vectors
void CrossProduct (double M[], double N[], double CS[]) {
CS[0]=M[1] * N[2] - M[2] * N[1];
CS[1]=M[2] * N[0] - M[0] * N[2];
CS[2]=M[0] * N[1] - M[1] * N[0];
}
//------ Returns the length of a vector
double GetVectorLength (double M[]) {
return sqrt( M[0] * M[0] + M[1] * M[1] + M[2] * M[2] );
}
//------ Returns the sum of two vectors
void AddVectors (double M[], double N[], double R[]){
R[0]= M[0] + N[0];
R[1]= M[1] + N[1];
R[2]= M[2] + N[2];
}
//------ Returns the vector scaled by the last parameter
void ScaleVector (double M[], double a, double N[]) {
N[0]= M[0] * a;
N[1]= M[1] * a;
N[2]= M[2] * a;
}
//------ Returns a normalized vector (length = 1)
void NormalizeVector (double M[],double R[3]) {
double norm = GetVectorLength(M);
if (norm == 0) norm =1.0;
ScaleVector( M, 1./ norm, R );
}
//------ Returns the unit normal vector of a triangle specified by the three
// points P1, P2, and P3.
void FindUnitNormal (double P1[3],double P2[3],double P3[3],double N[3]){
double D1[3],D2[3];
double R[3];
D1[0]=P1[0]-P2[0];
D1[1]=P1[1]-P2[1];
D1[2]=P1[2]-P2[2];
D2[0]=P2[0]-P3[0];
D2[1]=P2[1]-P3[1];
D2[2]=P2[2]-P3[2];
CrossProduct(D1,D2,R);
NormalizeVector(R,N);
}
//------ Initialization routine
void init () {
GLfloat mat_ambient[] = { 0.25, 0.20725, 0.20725, 0 };
GLfloat mat_diffuse[] = { 1.0, 0.829, 0.829, 0 };
GLfloat mat_specular[] = { 0.296648, 0.296648, 0.296648, 0 };
GLfloat light_diffuse[] = { 0.8, 0.8, 0.8, 1.0 };
GLfloat light_ambient[] = { 0.8, 0.8, 0.8, 1.0 };
GLfloat light_specular[] = { 0.5, 0.5, 0.5, 1.0 };
GLfloat light0_position[] = { -20.0, 20.0, 0.0, 0 };
GLfloat light1_position[] = { 10.0, 50.0, 0.0, 0 };
GLfloat shininess=0.088 * 128;
glClearColor( 0 , 0 , 0 , 0 ); // Black background
glShadeModel(GL_SMOOTH); // Smooth shading
glEnable(GL_MULTISAMPLE); // Enable multisample antialiasing
glEnable(GL_DEPTH_TEST); // Enable hidden surface removal
glEnable(GL_LIGHTING);
// set light 0
glEnable(GL_LIGHT0);
glLightfv( GL_LIGHT0, GL_POSITION, light0_position );
glLightfv( GL_LIGHT0, GL_DIFFUSE, light_diffuse );
glLightfv( GL_LIGHT0, GL_AMBIENT, light_ambient );
glLightfv( GL_LIGHT0, GL_SPECULAR, light_specular );
// set light 1
glEnable(GL_LIGHT1);
glLightfv( GL_LIGHT1, GL_POSITION, light1_position );
glLightfv( GL_LIGHT1, GL_DIFFUSE, light_diffuse );
glLightfv( GL_LIGHT1, GL_AMBIENT, light_ambient );
glLightfv( GL_LIGHT1, GL_SPECULAR, light_specular );
// set material
glMaterialfv( GL_FRONT, GL_AMBIENT, mat_ambient );
glMaterialfv( GL_FRONT, GL_DIFFUSE, mat_diffuse );
glMaterialfv( GL_FRONT, GL_SPECULAR, light_specular );
glMaterialf( GL_FRONT, GL_SHININESS, shininess );
egg=DrawEgg();
}
//------ Draw the scene
void display () {
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glLoadIdentity();
gluLookAt( 2, 4, 10, 0, 0, 0, 0, 1, 0 );
glPushMatrix();
glScalef( scale, scale, scale );
//Motion Options
glTranslatef(0.0, 0.0, -depth);
glRotatef(-theta, 1.0, 0.0, 0.0);
glRotatef(psi, 0.0, 1.0, 0.0);
if (showLight) {
// glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
}
else
{
// glDisable(GL_LIGHTING);
glDisable(GL_LIGHT0);
}
if (showLight1) {
// glEnable(GL_LIGHTING);
glEnable(GL_LIGHT1);
}
else
{
// glDisable(GL_LIGHTING);
glDisable(GL_LIGHT1);
}
glCallList(egg); // draw the egg
glPopMatrix();
glutSwapBuffers();
}
//------ GLUT Callback called when the window is resized
void reshape (int w, int h)
{
if (h == 0 || w == 0) return; //Nothing is visible then, so return
//Set a new projection matrix
glViewport( 0, 0, w, h );
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective( 60, h ? w / h : 0, 1, 20 );
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
//------ Routine for rotating the scene
void spinDisplay (){
int TimeNow = glutGet(GLUT_ELAPSED_TIME);
int WaitUntil=0;
if ( TimeNow >= WaitUntil ) {
spin += 1;
if ( spin > 360 )spin = spin - 360;
glutPostRedisplay();
WaitUntil = TimeNow + 1000 / 25; // 25 frames/s
}
}
int DrawEgg(){
/*
* Draw a eggs in 3D using Baker equation
*/
// ------ Parameters of the egg
double pi=3.141592;
int in=0;
double C[np*2][3],C0[np*2][3];
double NN[3];
GLuint egg;
double dthe= 2*pi/nrot;
double delta =2.0 / np;
double lam= pA+1.0;
double T=1.0/(pE+1.0);
double ex1=1.0/(1.0+lam);
double ex2=lam/(1.0+lam);
double x,x1,x2,y,t;
//------ Draw the egg
egg = glGenLists(1);
glNewList( egg, GL_COMPILE );
for ( double i=0 ; i <= np ; i ++ ) {
x=-1.0+i * delta;
x1=1.0+x;
x2=1.0-x;
y=T*pow(x1,ex1)*pow(x2,ex2);
cout << setprecision(6) << fixed;
// OpenGL display list for the egg
for ( int j=0;j<=nrot ;j++ )
{
t=dthe*j;
C[j][0] =x;
C[j][1] =y*cos(t);
C[j][2] =y*sin(t);
}
glBegin(GL_TRIANGLE_STRIP); // draws the band between the circles C0 and C
if(sqstripes) {
double r1= rand() / double(RAND_MAX);
double r2= rand() / double(RAND_MAX);
double r3= rand() / double(RAND_MAX);
GLfloat mat_ambient[] = { r1 , r2, r3, 0 };
glMaterialfv( GL_FRONT, GL_AMBIENT, mat_ambient );
}
if (i>0) {
for ( int j=0;j<=nrot ;j++ )
{
in=j;
if(!sqstripes) {
double r1= rand() / double(RAND_MAX);
double r2= rand() / double(RAND_MAX);
double r3= rand() / double(RAND_MAX);
GLfloat mat_ambient[] = { r1 , r2, r3, 0 };
glMaterialfv( GL_FRONT, GL_AMBIENT, mat_ambient );
}
FindUnitNormal (C0[in],C0[j+1],C[in],NN);
glNormal3d(NN[0],NN[1],NN[2]);
glVertex3f( C0[j][0],C0[j][1],C0[j][2] );
glVertex3f( C[j][0],C[j][1],C[j][2] );
glVertex3f( C0[j+1][0],C0[j+1][1],C0[j+1][2]);
glVertex3f( C[j+1][0],C[j+1][1],C[j+1][2]);
}
}
for ( int j=0;j<=nrot ;j++ )
{
C0[j][0]=C[j][0];
C0[j][1]=C[j][1];
C0[j][2]=C[j][2];
}
glEnd();
}
//Connect the last with the first
glEndList();
return egg;
}
//------ GLUT callback for the mouse
void mouse (GLint button, GLint state, GLdouble x, GLdouble y )
{
if ( button == GLUT_LEFT_BUTTON ) {
if ( state == GLUT_DOWN ) glutIdleFunc( spinDisplay ) ;
}
else if ( button == GLUT_RIGHT_BUTTON ) {
// if ( state == GLUT_DOWN ) glutIdleFunc(undef) ;
}
}
/* Callbacks */
void mouseCallback(int button, int state, int x, int y)
{
downX = x; downY = y;
leftButton = ((button == GLUT_LEFT_BUTTON) && (state == GLUT_DOWN));
rightButton = ((button == GLUT_RIGHT_BUTTON) && (state == GLUT_DOWN));
middleButton = ((button == GLUT_MIDDLE_BUTTON) && (state == GLUT_DOWN));
}
void motionCallback(int x, int y)
{
if (leftButton) //Rotate
{
psi += (x-downX)/4.0;
theta += (downY-y)/4.0;
}
if (rightButton) //Scale
{
if (depth + (downY - y)/10.0 < zFar-10 && depth + (downY - y)/10.0 > zNear+3)
depth += (downY - y)/10.0;
}
downX = x;
downY = y;
glutPostRedisplay();
}
void control_cb( int control )
{
egg=DrawEgg();
}
int main (int argc, char **argv)
{
//Initialize GLUT
glutInit(&argc, argv);
//double buffering used to avoid flickering problem in animation
glutInitDisplayMode(
GLUT_DOUBLE // Double buffering
| GLUT_RGB // RGB color mode
| GLUT_DEPTH // Hidden surface removal
| GLUT_MULTISAMPLE // Multisample antialiasing
);
// window size
glutInitWindowSize( 600, 600 );
// create the window
main_window = glutCreateWindow("Egg shapes");
init();
//Assign the function used in events
glutDisplayFunc(display);
glutReshapeFunc( reshape );
// glutMouseFunc( mouse );
// motion
glutMouseFunc(mouseCallback);
glutMotionFunc(motionCallback);
/* Here's the GLUI code */
/****************************************/
GLUI *glui = GLUI_Master.create_glui( "Command & Control",300,500 );
glui->add_statictext("Parameters for aperture shape");
spi[0]= new GLUI_Spinner( glui, "Asymmetric parameter (A):", &pA,200,control_cb);
spi[0] ->set_float_limits( -10, 10. );
spi[0]->set_speed(0.05);
spi[1]= new GLUI_Spinner( glui, "Ellipticity parameter (E):", &pE,200,control_cb );
spi[1]->set_float_limits( -10, 10. );
spi[1]->set_speed(0.05);
glui->add_separator();
new GLUI_Checkbox( glui, "Show Lights 0 ", &showLight);
new GLUI_Checkbox( glui, "Show Lights 1 ", &showLight1);
glui->add_separator();
new GLUI_Checkbox( glui, "Square/Stripes ", &sqstripes,0,control_cb);
glui->add_separator();
(new GLUI_Spinner( glui, "Scaling:", &scale,206, control_cb )) ->set_float_limits( 0, 20. );
glui->add_separator();
glui->add_button( "Quit", 0,(GLUI_Update_CB)exit );
glui->set_main_gfx_window( main_window );
/* We register the idle callback with GLUI, *not* with GLUT */
GLUI_Master.set_glutIdleFunc( myGlutIdle );
//Let start glut loop
glutMainLoop();
return 0;
}
Pingback: Modelling Forms in Nature: Easter Chocolate Eggs 2021 | Danilo Roccatano