BP

Previous

Next


use OpenGL;

#8 Seashell

This program draws a seashell.
I was inspired by the paper: Modeling seashells [pdf] by Deborah R. Fowlery, Hans Meinhardtz and Przemyslaw Prusinkiewiczy.
Another source I recommend is the golden oldie: D'Arcy Thompson - On growth and form (1945). (especially Chapter XI p.748~)

 seashell  seashell  seashell

seashell.pl


#!/usr/local/bin/perl
#
# shell.pl : draw a seashell
# (c) 2013 jl_morel@bribes.org - http://http://bribes.org/perl
use strict;
use warnings;
use OpenGL qw/ :all /;
use Math::Trig;

#------ Base vectors

my @i = ( 1, 0, 0 );
my @j = ( 0, 1, 0 );
my @k = ( 0, 0, 1 );
my @O = ( 0, 0, 0 );

# ------ Parameters of the seashell

my $tmax = 16 * pi;    # 8 rounds

my $r0 = 0.06;         # initial distance to the axis.
my $kr = 0.05;         # $r = $r0 * exp( $kr * $t )

my $z0 = 0.045;        # initial step
my $kz = 0.075;         # $z = $z0 * exp( $kz * $t )

my @Ap;                # shape of the aperture (a circle here )

my $n  = 32;
my $ra = 0.02;         # initial radius of the aperture
my $ka = 0.08;         # aperture radius = $ra * exp( $ka * $t )

for ( 0 .. 2 * $n - 1 )
{                      # here the aperture is a circle (=polygon with 2*$n sides)
  push @Ap, [ $ra * cos( $_ * pi / $n ), $ra * sin( $_ * pi / $n ), 0 ];
}

my @apex = ( 0, 2, 0 );    # shell apex

##--- Parametric equation of the helico-spiral

sub R {
  my $t = shift;
  return $apex[0] + $r0 * exp( $kr * $t ) * cos($t),
         $apex[1] - $z0 * exp( $kz * $t ),
         $apex[2] + $r0 * exp( $kr * $t ) * sin($t);
}

##--- First derivative

sub Rp {
  my $t = shift;
  return $r0 * exp( $kr * $t ) * ( $kr * cos($t) - sin($t) ),
         -$z0 * $kz * exp( $kz * $t ),
         $r0 * exp( $kr * $t ) * ( cos($t) + $kr * sin($t) );
}

##--- Second derivative

sub Rs {
  my $t = shift;
  return
    $r0 * exp( $kr * $t ) * ( ($kr * $kr - 1) * cos($t) - 2 * $kr * sin($t) ),
    -$z0 * $kz * $kz * exp( $kz * $t ),
    $r0 * exp( $kr * $t ) * ( ($kr * $kr - 1) * sin($t) + 2 * $kr * cos($t)  );
}

my $shell;    # OpenGL display list for the shell

#------ Draw the shell

sub DrawShell {

  my $shell = glGenLists(1);
  glNewList( $shell, GL_COMPILE );
  my @C0;     # previous aperture circle

  for ( my $t = 0 ; $t <= $tmax ; $t += 0.1 ) {
    my @R = R($t);
    my @T = NormalizeVector( Rp($t) );                        # tangent
    my @B = NormalizeVector( CrossProduct( @T, Rs($t) ) );    # binormal
    my @N = CrossProduct( @B, @T );                           # normal

    my @C;    # the current aperture circle
              # in the ( @R; @T, @N, @B ) coordinate system
    foreach (@Ap) {
      push @C,
        [
        AddVectors(
          AddVectors(
            ScaleVector( @N, exp( $ka * $t ) * $_->[0] ),
            ScaleVector( @B, exp( $ka * $t ) * $_->[1] )
          ),
          @R
        )
        ];
    }

    glBegin(GL_TRIANGLE_STRIP);  # draws the band between the circles @C0 and @C
    if ($t) {
      for ( my $i = -1 ; $i < 2 * $n - 1 ; $i++ ) {
        glNormal3d(
          FindUnitNormal( @{ $C0[$i] }, @{ $C0[ $i + 1 ] }, @{ $C[$i] } ) );
        glVertex3f( @{ $C0[$i] } );
        glVertex3f( @{ $C[$i] } );
        glVertex3f( @{ $C0[ $i + 1 ] } );
        glVertex3f( @{ $C[ $i + 1 ] } );
      }
    }
    glEnd();
    @C0 = @C;                    # for the next step
  }
  glEndList;
  return $shell;
}

#------ Draw the scene

my $spin = 0;                    # rotation angle

sub display {
  glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
  glLoadIdentity();
  gluLookAt( 2, 4, 10, 0, 0, 0, 0, 1, 0 );
  glPushMatrix();
    glScalef( 1.9, 1.9, 1.9 );
    glRotatef( $spin, -sqrt(2) / 2, sqrt(2) / 2, 0 );
    glCallList($shell);            # draw the shell
  glPopMatrix();
  glutSwapBuffers();

  # debug code
#  if ( ( my $e = glGetError() ) != GL_NO_ERROR ) {
#    print "error : ", gluErrorString($e), "\n";
#  }
}

#------ Initialization routine

# pearl
my @mat_ambient  = ( 0.25,     0.20725,  0.20725,  0 );
my @mat_diffuse  = ( 1,        0.829,    0.829,    0 );
my @mat_specular = ( 0.296648, 0.296648, 0.296648, 0 );
my $shininess    = 0.088 * 128;

sub init {
  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);

  my @light_diffuse  = ( 0.8, 0.8, 0.8, 1.0 );
  my @light_ambient  = ( 0.8, 0.8, 0.8, 1.0 );
  my @light_specular = ( 0.5, 0.5, 0.5, 1.0 );

  # set light 0
  glEnable(GL_LIGHT0);
  my @light0_position = ( -20.0, 20.0, 0.0, 0 );
  glLightfv_p( GL_LIGHT0, GL_POSITION, @light0_position );
  glLightfv_p( GL_LIGHT0, GL_DIFFUSE,  @light_diffuse );
  glLightfv_p( GL_LIGHT0, GL_AMBIENT,  @light_ambient );
  glLightfv_p( GL_LIGHT0, GL_SPECULAR, @light_specular );

  # set light 1
  glEnable(GL_LIGHT1);
  my @light1_position = ( 10.0, 50.0, 0.0, 0 );
  glLightfv_p( GL_LIGHT1, GL_POSITION, @light1_position );
  glLightfv_p( GL_LIGHT1, GL_DIFFUSE,  @light_diffuse );
  glLightfv_p( GL_LIGHT1, GL_AMBIENT,  @light_ambient );
  glLightfv_p( GL_LIGHT1, GL_SPECULAR, @light_specular );

  # set material
  glMaterialfv_p( GL_FRONT, GL_AMBIENT,  @mat_ambient );
  glMaterialfv_p( GL_FRONT, GL_DIFFUSE,  @mat_diffuse );
  glMaterialfv_p( GL_FRONT, GL_SPECULAR, @light_specular );
  glMaterialf( GL_FRONT, GL_SHININESS, $shininess );

  $shell = DrawShell();
}

#------ GLUT Callback called when the window is resized

sub reshape {
  my ( $w, $h ) = @_;
  glViewport( 0, 0, $w, $h );
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective( 45, $h ? $w / $h : 0, 1, 20 );
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

#------ Routine for rotating the scene

my $WaitUntil = 0;

sub spinDisplay {
  my $TimeNow = glutGet(GLUT_ELAPSED_TIME);
  if ( $TimeNow >= $WaitUntil ) {
    $spin += 1;
    $spin = $spin - 360 if ( $spin > 360 );
    glutPostRedisplay();
    $WaitUntil = $TimeNow + 1000 / 25;    # 25 frames/s
  }
}

#------ GLUT callback for the mouse

sub mouse {
  my ( $button, $state, $x, $y ) = @_;
  if ( $button == GLUT_LEFT_BUTTON ) {
    glutIdleFunc( \&spinDisplay ) if ( $state == GLUT_DOWN );
  }
  elsif ( $button == GLUT_RIGHT_BUTTON ) {
    glutIdleFunc(undef) if ( $state == GLUT_DOWN );
  }
}

#------ Main program

glutInit();
glutInitDisplayMode(
      GLUT_DOUBLE         # Double buffering
    | GLUT_RGB            # RGB color mode
    | GLUT_DEPTH          # Hidden surface removal
    | GLUT_MULTISAMPLE    # Multisample antialiasing
);
glutInitWindowSize( 300, 300 );
glutCreateWindow("Seashell");
init();
glutDisplayFunc( \&display );
glutReshapeFunc( \&reshape );
glutMouseFunc( \&mouse );
glutIdleFunc( \&spinDisplay );
glutMainLoop();

# ============ Some 3D math routines

#------ Returns the cross product of 2 vectors

sub CrossProduct {
  return (
    $_[1] * $_[5] - $_[2] * $_[4],
    $_[3] * $_[2] - $_[0] * $_[5],
    $_[0] * $_[4] - $_[1] * $_[3]
  );
}

#------ Returns the length of a vector

sub GetVectorLength {
  return sqrt( $_[0] * $_[0] + $_[1] * $_[1] + $_[2] * $_[2] );
}

#------ Returns the sum of two vectors

sub AddVectors {
  return ( $_[0] + $_[3], $_[1] + $_[4], $_[2] + $_[5] );
}

#------ Returns the vector scaled by the last parameter

sub ScaleVector {
  return ( $_[0] * $_[3], $_[1] * $_[3], $_[2] * $_[3] );
}

#------ Returns a normalized vector (length = 1)

sub NormalizeVector {
  return ( 0, 0, 0 ) if ( my $norm = GetVectorLength(@_) ) == 0;
  return ScaleVector( @_, 1 / $norm );
}

#------ Returns the unit normal vector of a triangle specified by the three
# points P1, P2, and P3.

sub FindUnitNormal {
  return NormalizeVector(
    CrossProduct(
      $_[0] - $_[3], $_[1] - $_[4], $_[2] - $_[5],    # P1-P2
      $_[3] - $_[6], $_[4] - $_[7], $_[5] - $_[8]     # P2-P3
    )
  );
}


The script as .txt for download: seashell.pl.txt

Back to Top


BP 2013 J-L Morel - Contact : jl_morel@bribes.org [Validation HTML 4.0!]