Opisane w poprzednim odcinku kursu krzywe i powierzchnie B´eziera nie są niestety pozbawione wad. Uzależnienie kształtu krzywej lub powierzchni od wszystkich punktów kontrolnych utrudnia kontrolę na kształtem obiektu, zwłaszcza przy rosnącym stopniu krzywej lub powierzchni. Utrudnione jest także gładkie łączenie wielu obiektów w jedną całość.
Wymienionych wad pozbawione są krzywe i powierzchnie NURBS (ang. Non-Uniform Rational B-Spline), których obsługę zawiera biblioteka GLU. W szczególności stopień obiektów NURBS nie rośnie wraz ze wzrostem liczby punktów kontrolnych, bowiem krzywa (powierzchnia) NURBS składa się z segmentów. Kształt ewaluowanej krzywej lub powierzchni zależy od pewnej liczby punktów kontrolnych w sąsiedztwie oraz od tzw. węzłów (ang. knots) określających wpływ kolejnych punktów kontrolnych na kształt segmentu krzywej lub powierzchni. Na każdy punkt kontrolny przypadają dwa węzły, a im mniejsza różnica wartości pomiędzy nimi, tym mniejsza jest krzywizna segmentu krzywej lub powierzchni.
W praktyce obiekty NURBS pozwalają na modelowanie zarówno podstawowych obiektów geometrycznych (linie, koła, elipsy, kule) jak i dowolnie skomplikowanych kształtów.
Obiekt NURBS
Krzywą lub powierzchnię NURBS reprezentuje struktura (w przypadku języka C++ klasa) GLUnurbs. Dostępna jest także alternatywna nazwa struktury/klasy GLUnurbsObj.
Obiekt NURBS tworzy wywołanie funkcji gluNewNurbsRenderer:
GLUnurbs * gluNewNurbsRenderer( void )
która zwraca wskaźnik do struktury (klasy) GLUnurbs, niezbędny w wywołaniach pozostałych funkcji obsługujących krzywe i powierzchnie NURBS. Po zakończeniu operacji na danym obiekcie NURBS należy zwolnić przydzieloną mu pamięć wywołując funkcję:
void gluDeleteNurbsRenderer( GLUnurbs * nurb )
Krzywe NURBS
Definicję krzywej NURBS rozpoczyna wywołanie funkcji gluBeginCurve:
void gluBeginCurve( GLUnurbs * nurb )
której jedynym parametrem jest wskaźnik do struktury (klasy) GLUnurbs.
Kształ krzywej NURBS definiuje funkcja gluNurbsCurve:
void gluNurbsCurve( GLUnurbs * nurb, GLint knotCount, GLfloat * knots, GLint stride, GLfloat * control, GLint order, GLenum type )
Parametr knotCount określa ilość węzłów, których wartości zawiera tablica knots, natomiast ilość punktów kontrolnych zawartych w tablicy control określa parametr order. Odstęp pomiędzy wartościami kolejnych punktów kontrolnych zawiera parametr stride.
Ostatni parametr type określa typ generowanych danych krzywej. Akceptowane są wszystkie jednowymiarowe ewaluatory zdefiniowane dla krzywych B´eziera:
GL_MAP1_VERTEX_3,
GL_MAP1_VERTEX_4,
GL_MAP1_INDEX,
GL_MAP1_COLOR_4,
GL_MAP1_NORMAL,
GL_MAP1_TEXTURE_COORD_1,
GL_MAP1_TEXTURE_COORD_2,
GL_MAP1_TEXTURE_COORD_3 i
GL_MAP1_TEXTURE_COORD_4.
Po zakończeniu definiowania krzywej NURBS trzeba wywołać funkcję gluEndCurve:
void gluEndCurve( GLUnurbs * nurb )
Powierzchnie NURBS
Definicję powierzchni NURBS rozpoczyna wywołanie funkcji gluBeginSurface:
void gluBeginSurface( GLUnurbs * nurb )
Kształt powierzchni NURBS określa funkcja gluNurbsSurface:
void gluNurbsSurface( GLUnurbs * nurb, GLint sKnotCount, GLfloat * sKnots, GLint tKnotCount, GLfloat * tKnots, GLint sStride, GLint tStride, GLfloat * control, GLint sOrder, GLint tOrder, GLenum type )
Parametry sKnotCount i tKnotCount określają ilości węzłów dla kierunku s i t, których wartości zawierają odpowiednio tablice sKnots i tKnots.
Ilości punktów kontrolnych dla kierunku s i t zawierają parametry sOrder i tOrder. Same punkty kontrole są zawarte w tablicy control, przy czym odstępy pomiędzy wartościami poszczególnych punktów kontrolnych w kierunkach s i t określają parametry sStride i tStride.
Ostatni parametr type określa typ generowanych danych powierzchni.
Dopuszczalne są wszystkie dwuwymiarowe ewaluatory zdefiniowane dla powierzchni B´eziera:
GL_MAP2_VERTEX_3,
GL_MAP2_VERTEX_4,
GL_MAP2_INDEX,
GL_MAP2_COLOR_4,
GL_MAP2_NORMAL,
GL_MAP2_TEXTURE_COORD_1,
GL_MAP2_TEXTURE_COORD_2,
GL_MAP2_TEXTURE_COORD_3 i
GL_MAP2_TEXTURE_COORD_4.
Po zakończeniu definiowania krzywej NURBS trzeba wywołać funkcję gluEndSurface:
void gluEndSurface( GLUnurbs * nurb )
Funkcje zwrotne
Biblioteka GLU zawiera obsługę funkcji zwrotnych wywoływanych na różnym etapie tworzenia krzywych i powierzchni NURBS. Dołączenie funkcji zwrotnej realizuje funkcja:
void gluNurbsCallback( GLUnurbs * nurb, GLenum which, void( * fn )() )
której parametr which określa rodzaj wykonywanej czynności lub rodzaj generowanych danych podczas renderingu obiektu NURBS, a odpowiednia funkcja zwrotna wskazywana jest w parametrze fn.
Funkcje zwrotne występują w dwóch wersjach. Pierwsza z nich zwraca tylko dane związane z charakterem swojego działania (np. współrzędne wierzchołków prymitywów). Wersje alternatywne zwracają dodatkowo dane użytkownika przekazane w parametrze userData funkcji:
void gluNurbsCallbackData( GLUnurbs * nurb, GLvoid * userData )
Funkcje zwrotne renderera obiektów NURBS określone są następującymi stałymi:
Początkowo wszystkie funkcje zwrotne są niezdefiniowane (wartość NULL). Oprócz
GLU_NURBS_ERROR (
GLU_ERROR) wszystkie funkcje zwrotne zostały wprowadzone w wersji 1.3 biblioteki, a wcześniej w rozszerzeniu EXT nurbs - tessellator. Funkcje zwrotne wprowadzone w wersji 1.3 są aktywne tylko wtedy, gdy tryb renderingu obiektu NURBS (
GLU_NURBS_MODE) wynosi
GLU_NURBS_TESSELLATOR.
Tabela 1: Zestawienie kodów błędów obiektów NURBS
Właściwości NURBS
Modyfikcję właściwości krzywej lub powierzchni NURBS umożliwia funkcja gluNurbsProperty:
void gluNurbsProperty( GLUnurbs * nurb, GLenum property, GLfloat value )
Parametr property określa rodzaj modyfikowanej właściwości obiektu NURBS wskazywanego w parametrze nurb. Nową wartość właściwości zawiera oczywiście parametr value. Zestawienie dopuszczalnych wartości właściwości obiektów NURBS wraz z wartościami domyślnymi przedstawiono w tabeli 2.
Właściwość GLU_NURBS_MODE
Właściwość
GLU_NURBS_MODE określa tryb renderingu obiektu NURBS. Możliwe są dwa tryby renderingu:
Stałe
GLU_NURBS_MODE,
GLU_NURBS_TESSELLATOR i
GLU_NURBS_RENDERER zostały wprowadzone w wersji 1.3 biblioteki GLU, a wcześniej w rozszerzeniu EXT nurbs tessellator.
Właściwość GLU_CULLING
Przyjmująca wartości logiczne właściwość
GLU_CULLING decyduje czy krzywa lub powierzchnia NURBS ma być renderowana, gdy jej punkty kontrolne znajdują się poza obszarem renderingu. Domyślnie jest to wyłączone - wartość
GL_FALSE.
Tabela 2: Zestawienie właściwości obiektów NURBS
Właściwość GLU_SAMPLING_METHOD
Właściwość
GLU_SAMPLING_METHOD określa metodę podziału powierzchni NURBS na wielokąty. Dopuszczalnych jest pięć metod:
Właściwość GLU_SAMPLING_TOLERANCE
Właściwość
GLU_SAMPLING_TOLERANCE ustala maksymalną długość, w pikselach lub w przestrzeni obiektu, boków wielokątów składających się na renderowaną powierzchnię. Właściwość ta jest używana, gdy metodą podziału powierzchni NURBS jest
GLU_PATH_LENGTH lub
GLU_OBJECT_PATH_LENGTH.
Właściwość GLU_PARAMETRIC_TOLERANCE
Właściwość
GLU_PARAMETRIC_TOLERANCE jest używana, gdy metodą podziału powierzchni NURBS jest
GLU_PARAMETRIC_ERROR lub
GLU_OBJECT - PARAMETRIC ERROR i określa maksymalną odległość, w pikselach lub w przestrzeni obiektu, pomiędzy wielokątami podziału a całą renderowaną powierzchnią.
Właściwości GLU_U_STEP i GLU_V_STEP
Właściwości
GLU_U_STEP i
GLU_V_STEP określają ilość punktów próbkowania na jednostkę długości odpowiednio dla parametru u i v funkcji parametrycznej. Wartości te używane są, gdy metodą podziału powierzchni NURBS na wielokąty jest
GLU_DOMAIN_DISTANCE.
Właściwość GLU_AUTO_LOAD_MATRIX
Właściwość
GLU_AUTO_LOAD_MATRIX przyjmuje wartości logiczne określające, czy macierz próbkowania używana przy podziale obiektów NURBS oraz macierz obcinania służąca do usuwania niewidocznych elementów NURBS mają być automatycznie obliczane przez bibliotekę GLU (
GL_FALSE), czy też potrzebne do jej obliczenia elementy, tj. macierz modelowania, macierz rzutowania oraz obszar renderingu zostaną wskazane przez użytkownika (
GL_TRUE).
Wartości potrzebne do obliczenia macierzy próbkowania i obcinania przekazuje funkcja:
void gluLoadSamplingMatrices( GLUnurbs * nurb, const GLfloat * model, const GLfloat * perspective, const GLint * view )
gdzie model to wskaźnik na macierz modelowania, perspective wskaźnik na macierz rzutowania, a view jest wskaźnikiem do danych obszaru renderingu.
Właściwość GLU_DISPLAY_MODE
Właściwość
GLU_DISPLAY_MODE reguluje sposób renderowania powierzchni NURBS. Możliwe są trzy sposobu renderingu określane stałymi:
Pobieranie właściwości NURBS
Pobieranie właściwości obiektu NURBS umożliwia funkcja gluGetNurbsProperty:
void gluGetNurbsProperty( GLUnurbs * nurb, GLenum property, GLfloat * data )
której parametr property określa jakiego rodzaju właściwość krzywej lub powierzchni NURBS ma zostać zwrócona. Zakres wartości tego parametru jest taki sam jak dopuszczalne wartości analogicznego parametru funkcji gluNurbsProperty.
Wycinanie fragmentów powierzchni
Do wycinania fragmentów powierzchni NURBS można użyć krzywej NURBS definiowanej przy użyciu funkcji gluNurbsCurve lub funkcji gluPwlCurve określającej krzywą PWL (ang. piecewise linear), czyli krzywej odcinkami liniowej:
void gluPwlCurve( GLUnurbs * nurb, GLint count, GLfloat * data, GLint stride, GLenum type )
Parametr count określa ilość punktów krzywej PWL, których współrzędne zawiera tablica data. Odstęp pomiędzy danymi kolejnych punktów krzywej określa parametr stride.
Ostatni parametr type określa rodzaj współrzędnych krzywej PWL. Możliwe są dwie wartości:
Krzywe wycinające można łączyć, przy czym musi zachodzić warunek współdzielenia punktów końcowych a cały zdefiniowany obszar wycinania jest zamknięty (pierwszy punkt krzywej musi być równy ostatniemu punktowi). Krzywa wycinająca nie może mieć wewnętrznych przecięć. Krzywa lub krzywe określone zgodnie z ruchem wskazówek zegara powodują wycięcie dziury w powierzchni. Natomiast krzywa określona przeciwnie do ruchów wskazówek zegara powoduje usunięcie zewnętrznego obszaru. Zazwyczaj najpierw definiuje się krzywą określającą wycięcie zewnętrzne (w szczególności może to być cała powierzchnia NURBS), a następnie określa krzywe wycinające dziury w powierzchni.
Funkcje gluNurbsCurve i gluPwlCurve lub ich sekwencje muszą zostać wywołane pomiędzy funkcjami gluBeginTrim i gluEndTrim:
void gluBeginTrim( GLUnurbs * nurb )
void gluEndTrim( GLUnurbs * nurb )
przy czym każda grupa funkcji gluNurbsCurve i gluPwlCurve określająca odrębny fragment wycinanej powierzchni NURBS musi być umieszczona w odrębnym wywołaniu pary funkcji gluBeginTrim i gluEndTrim.
Programy przykładowe
Pierwszy program przykładowy (plik krzywa nurbs.cpp) przedstawia krzywą NURBS opartą o pięć punktów kontrolnych i dziesięć węzłów. Podobnie jak w programie przykładowym z poprzedniego odcinka kursu rysujący krzywą B´eziera, tutaj także użytkownik może zmieniać położenie punktów kontrolnych. Dodatkowo możliwa jest modyfikacja wartości parametru
GLU_SAMPLING_TOLERANCE, który ma bezpośredni wpływ na wygląd renderowanej krzywej (wartość parametru jest wyświetlana na dole okna programu). Przy wartości 10 krzywa jest optycznie gładka (patrz rysunek 1), natomiast przy większych wartościach tego parametru krzywa przechodzi w łamaną - patrz rysunek 2, gdzie wartość
GLU_SAMPLING_TOLERANCE wynosi 150.
Plik krzywa_nurbs.cpp
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "colors.h"
enum
{
EXIT
};
int button_state = GLUT_UP;
int button_x, button_y;
GLfloat points[ 5 * 3 ] =
{
50.0, 50.0, 0.0, 250.0, 250.0, 0.0, 50.0, 450.0, 0.0, 450.0, 50.0, 0.0, 450.0, 450.0, 0.0
};
GLfloat knots[ 5 * 2 ] =
{
0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0
};
GLfloat sampling_tolerance = 20.0;
enum
{
NONE,
CURVE,
POINT_0,
POINT_1,
POINT_2,
POINT_3,
POINT_4
};
int select_object = NONE;
void DrawString( GLfloat x, GLfloat y, char * string )
{
glRasterPos2f( x, y );
int len = strlen( string );
for( int i = 0; i < len; i++ )
glutBitmapCharacter( GLUT_BITMAP_9_BY_15, string[ i ] );
}
void Display()
{
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClear( GL_COLOR_BUFFER_BIT );
glInitNames();
glPushName( NONE );
glLoadName( CURVE );
glLineWidth( 2.0 );
glColor3fv( Black );
GLUnurbsObj * nurbs = gluNewNurbsRenderer();
gluBeginCurve( nurbs );
gluNurbsProperty( nurbs, GLU_SAMPLING_TOLERANCE, sampling_tolerance );
gluNurbsCurve( nurbs, 10, knots, 3, points, 5, GL_MAP1_VERTEX_3 );
gluEndCurve( nurbs );
gluDeleteNurbsRenderer( nurbs );
glColor3fv( Red );
for( int i = 0; i < 5; i++ )
{
glLoadName( POINT_0 + i );
glRectf( points[ i * 3 ] - 5, points[ i * 3 + 1 ] - 5, points[ i * 3 ] + 5, points[ i * 3 + 1 ] + 5 );
}
char string[ 100 ];
glColor3fv( Black );
sprintf( string, "GLU_SAMPLING_TOLERANCE = %f", sampling_tolerance );
DrawString( 2, 2, string );
glFlush();
glutSwapBuffers();
}
void Reshape( int width, int height )
{
glViewport( 0, 0, width, height );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
gluOrtho2D( 0, width, 0, height );
Display();
}
void Selection( int x, int y )
{
const int BUFFER_LENGTH = 64;
GLuint select_buffer[ BUFFER_LENGTH ];
glSelectBuffer( BUFFER_LENGTH, select_buffer );
int viewport[ 4 ];
glGetIntegerv( GL_VIEWPORT, viewport );
int width = viewport[ 2 ];
int height = viewport[ 3 ];
glMatrixMode( GL_PROJECTION );
glPushMatrix();
glLoadIdentity();
gluPickMatrix( x, height - y, 2, 2, viewport );
gluOrtho2D( 0, width, 0, height );
glRenderMode( GL_SELECT );
Display();
GLint hits = glRenderMode( GL_RENDER );
glMatrixMode( GL_PROJECTION );
glPopMatrix();
select_object = NONE;
if( hits > 0 )
if( select_buffer[ 3 ] > CURVE )
select_object = select_buffer[ 3 ];
}
void MouseButton( int button, int state, int x, int y )
{
if( button == GLUT_LEFT_BUTTON )
{
Selection( x, y );
glutPostRedisplay();
button_state = state;
if( state == GLUT_DOWN )
{
button_x = x;
button_y = y;
}
}
}
void MouseMotion( int x, int y )
{
if( x > 0 & x < glutGet( GLUT_WINDOW_WIDTH ) & y > 0 & y < glutGet( GLUT_WINDOW_HEIGHT ) )
if( button_state == GLUT_DOWN )
{
switch( select_object )
{
case POINT_0:
points[ 0 ] += x - button_x;
points[ 1 ] += button_y - y;
break;
case POINT_1:
points[ 3 ] += x - button_x;
points[ 4 ] += button_y - y;
break;
case POINT_2:
points[ 6 ] += x - button_x;
points[ 7 ] += button_y - y;
break;
case POINT_3:
points[ 9 ] += x - button_x;
points[ 10 ] += button_y - y;
break;
case POINT_4:
points[ 12 ] += x - button_x;
points[ 13 ] += button_y - y;
break;
}
button_x = x;
button_y = y;
glutPostRedisplay();
}
}
void Keyboard( unsigned char key, int x, int y )
{
switch( key )
{
case '+':
sampling_tolerance += 10.0;
break;
case '-':
if( sampling_tolerance > 10.0 )
sampling_tolerance -= 10.0;
break;
}
Display();
}
void Menu( int value )
{
switch( value )
{
case EXIT:
exit( 0 );
}
}
int main( int argc, char * argv[] )
{
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
glutInitWindowSize( 500, 500 );
glutCreateWindow( "Krzywa NURBS" );
glutDisplayFunc( Display );
glutReshapeFunc( Reshape );
glutKeyboardFunc( Keyboard );
glutMouseFunc( MouseButton );
glutMotionFunc( MouseMotion );
glutCreateMenu( Menu );
glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
glutMainLoop();
return 0;
}
Drugi program przykładowy (plik powierzchnia nurbs.cpp) wyświetla przykładową powierzchnię NURBS, która opcjonalnie może mieć wyciętą dziurę. Ponadto powierzchnię można renderować z różnymi metodami podziału na wielokąty, przy czym program wykrywa wersję biblioteki GLU i gdy jest to wersja 1.3, zawartość menu jest uzupełniana o opcje
GLU_OBJECT_PARAMETRIC_ERROR i
GLU_OBJECT_PATH_LENGTH.
Porównajmy zatem jak wygląda przykładowa powierzchnia NURBS rysowana trzema metodami podziału na wielokąty dostępnymi w wersji 1.2 biblioteki GLU, przy czym należy pamiętać, że program nie zmienia właściwości wpływających na sposób tego podziału. Właściwości:
GLU_SAMPLING_TOLERANCE,
GLU_PARAMETRIC_TOLERANCE,
GLU_U_STEP i
GLU_V_STEP przyjmują wartości domyślne. Dla czytelności efektów podziału rysunki 3 - 8 przedstawiają powierzchnię NURBS renderowaną w postaci siatki krawędzi wielokątów.
Przy powiększeniu (rysunki 3 - 5) wszystkie powierzchnie wyglądają podobnie, choć wyraźnie widać, że metoda podziału
GLU_DOMAIN_DISTANCE przy parametrach domyślnych wygenerowała większą ilość wielokątów. Większe różnice widać dopiero przy znacznym zmniejszeniu renderowanej powierzchni (rysunki 6 - 8). Ponownie wyróżnia się metoda podziału
GLU_DOMAIN_DISTANCE, która przypomnijmy ma stałą liczbę punktów podziału i przy domyślnych wartościach parametrów
GLU_U_STEP i
GLU_V_STEP, nie nadaje się najlepiej do renderingu małej powierzchni.
Program umożliwia także rendering wypełnionej powierzchni z możliwością wyboru rodzaju materiału. Przykładowy wygląd takiej powierzchni przedstawia rysunek 9. Ta sama powierzchnia ale już z wyciętą dziurą została przedstawiona na rysunku 10. Ostatni rysunek - 11 - przedstawia efekt rysowania powierzchni NURBS z dziurą, gdy rysowane są zdefiniowane w programie krzywe wycinania (sposób renderowania powierzchni
GLU_OUTLINE_PATCH).
Plik powierzchnia_nurbs.cpp
#include <GL/glut.h>
#include <GL/glu.h>
#include <GL/glext.h>
#ifndef WIN32
#define GLX_GLXEXT_LEGACY
#include <GL/glx.h>
#define wglGetProcAddress glXGetProcAddressARB
#endif
#include <stdlib.h>
#include <stdio.h>
#include "colors.h"
#include "materials.h"
PFNGLWINDOWPOS2IPROC glWindowPos2i = NULL;
enum
{
HOLE,
BRASS,
BRONZE,
POLISHED_BRONZE,
CHROME,
COPPER,
POLISHED_COPPER,
GOLD,
POLISHED_GOLD,
PEWTER,
SILVER,
POLISHED_SILVER,
EMERALD,
JADE,
OBSIDIAN,
PEARL,
RUBY,
TURQUOISE,
BLACK_PLASTIC,
BLACK_RUBBER,
FULL_WINDOW,
ASPECT_1_1,
EXIT
};
int aspect = FULL_WINDOW;
#ifdef near
#undef near
#endif
#ifdef far
#undef far
#endif
const GLdouble left = - 2.0;
const GLdouble right = 2.0;
const GLdouble bottom = - 2.0;
const GLdouble top = 2.0;
const GLdouble near = 3.0;
const GLdouble far = 7.0;
GLfloat rotatex = 0.0;
GLfloat rotatey = 0.0;
int button_state = GLUT_UP;
int button_x, button_y;
GLfloat scale = 1.0;
const GLfloat * ambient = BrassAmbient;
const GLfloat * diffuse = BrassDiffuse;
const GLfloat * specular = BrassSpecular;
GLfloat shininess = BrassShininess;
int sampling_method = GLU_PATH_LENGTH;
int display_mode = GLU_FILL;
GLfloat points[ 4 * 4 * 3 ] =
{
- 1.0, - 1.0, 0.0, - 1.0, - 0.5, 0.0, - 1.0, 0.5, 0.0, - 1.0, 1.0, 0.0,
- 0.5, - 1.0, 0.0, - 0.5, - 0.5, 1.5, - 0.5, 0.5, 1.5, - 0.5, 1.0, 0.0,
0.5, - 1.0, 0.0, 0.5, - 0.5, 1.5, 0.5, 0.5, 1.5, 0.5, 1.0, 0.0,
1.0, - 1.0, 0.0, 1.0, - 0.5, 0.0, 1.0, 0.5, 0.0, 1.0, 1.0, 0.0
};
GLfloat knots[ 4 * 2 ] =
{
0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0
};
GLfloat outline_pwl[ 10 ] =
{
0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0
};
GLfloat hole_pwl[ 10 ] =
{
0.25, 0.25, 0.25, 0.75, 0.75, 0.75, 0.75, 0.25, 0.25, 0.25
};
bool GLU_1_3 = false;
#ifndef GLU_OBJECT_PARAMETRIC_ERROR
#define GLU_OBJECT_PARAMETRIC_ERROR 100208
#endif
#ifndef GLU_OBJECT_PATH_LENGTH 100209
#define GLU_OBJECT_PATH_LENGTH 100209
#endif
bool hole = false;
void DrawString( GLint x, GLint y, char * string )
{
glWindowPos2i( x, y );
int len = strlen( string );
for( int i = 0; i < len; i++ )
glutBitmapCharacter( GLUT_BITMAP_9_BY_15, string[ i ] );
}
void DisplayScene()
{
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glEnable( GL_DEPTH_TEST );
glTranslatef( 0, 0, -( near + far ) / 2 );
glRotatef( rotatex, 1.0, 0, 0 );
glRotatef( rotatey, 0, 1.0, 0 );
glScalef( scale, scale, scale );
if( display_mode == GLU_FILL )
{
glEnable( GL_LIGHTING );
glEnable( GL_LIGHT0 );
glEnable( GL_NORMALIZE );
glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT, ambient );
glMaterialfv( GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse );
glMaterialfv( GL_FRONT_AND_BACK, GL_SPECULAR, specular );
glMaterialf( GL_FRONT_AND_BACK, GL_SHININESS, shininess );
glEnable( GL_AUTO_NORMAL );
}
glColor3fv( Black );
GLUnurbsObj * nurbs = gluNewNurbsRenderer();
gluBeginSurface( nurbs );
gluNurbsProperty( nurbs, GLU_DISPLAY_MODE, display_mode );
gluNurbsProperty( nurbs, GLU_SAMPLING_METHOD, sampling_method );
gluNurbsSurface( nurbs, 8, knots, 8, knots, 4 * 3, 3, points, 4, 4, GL_MAP2_VERTEX_3 );
if( hole )
{
gluBeginTrim( nurbs );
gluPwlCurve( nurbs, 5, outline_pwl, 2, GLU_MAP1_TRIM_2 );
gluEndTrim( nurbs );
gluBeginTrim( nurbs );
gluPwlCurve( nurbs, 5, hole_pwl, 2, GLU_MAP1_TRIM_2 );
gluEndTrim( nurbs );
}
gluEndSurface( nurbs );
gluDeleteNurbsRenderer( nurbs );
glDisable( GL_AUTO_NORMAL );
glDisable( GL_NORMALIZE );
glDisable( GL_LIGHT0 );
glDisable( GL_LIGHTING );
glColor3fv( Blue );
glPointSize( 6.0 );
glBegin( GL_POINTS );
for( int i = 0; i < 12; i++ )
glVertex3fv( points + i * 3 );
glEnd();
glColor3fv( Black );
if( sampling_method == GLU_PATH_LENGTH )
DrawString( 2, 2, "GLU_SAMPLING_METHOD = GLU_PATH_LENGTH" );
else
if( sampling_method == GLU_PARAMETRIC_ERROR )
DrawString( 2, 2, "GLU_SAMPLING_METHOD = GLU_PARAMETRIC_ERROR" );
else
if( sampling_method == GLU_DOMAIN_DISTANCE )
DrawString( 2, 2, "GLU_SAMPLING_METHOD = GLU_DOMAIN_DISTANCE" );
if( GLU_1_3 )
{
if( sampling_method == GLU_OBJECT_PARAMETRIC_ERROR )
DrawString( 2, 2, "GLU_SAMPLING_METHOD = GLU_OBJECT_PARAMETRIC_ERROR" );
else
if( sampling_method == GLU_OBJECT_PATH_LENGTH )
DrawString( 2, 2, "GLU_SAMPLING_METHOD = GLU_OBJECT_PATH_LENGTH" );
}
if( display_mode == GLU_FILL )
DrawString( 2, 16, "GLU_DISPLAY_MODE = GLU_FILL" );
else
if( display_mode == GLU_OUTLINE_PATCH )
DrawString( 2, 16, "GLU_DISPLAY_MODE = GLU_OUTLINE_PATCH" );
else
if( display_mode == GLU_OUTLINE_POLYGON )
DrawString( 2, 16, "GLU_DISPLAY_MODE = GLU_OUTLINE_POLYGON" );
glFlush();
glutSwapBuffers();
}
void Reshape( int width, int height )
{
glViewport( 0, 0, width, height );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
if( aspect == ASPECT_1_1 )
{
if( width < height && width > 0 )
glFrustum( left, right, bottom * height / width, top * height / width, near, far );
else
if( width >= height && height > 0 )
glFrustum( left * width / height, right * width / height, bottom, top, near, far );
}
else
glFrustum( left, right, bottom, top, near, far );
DisplayScene();
}
void Keyboard( unsigned char key, int x, int y )
{
if( key == '+' )
scale += 0.05;
else
if( key == '-' && scale > 0.05 )
scale -= 0.05;
DisplayScene();
}
void MouseButton( int button, int state, int x, int y )
{
if( button == GLUT_LEFT_BUTTON )
{
button_state = state;
if( state == GLUT_DOWN )
{
button_x = x;
button_y = y;
}
}
}
void MouseMotion( int x, int y )
{
if( button_state == GLUT_DOWN )
{
rotatey += 30 *( right - left ) / glutGet( GLUT_WINDOW_WIDTH ) *( x - button_x );
button_x = x;
rotatex -= 30 *( top - bottom ) / glutGet( GLUT_WINDOW_HEIGHT ) *( button_y - y );
button_y = y;
glutPostRedisplay();
}
}
void Menu( int value )
{
switch( value )
{
case GLU_FILL:
case GLU_OUTLINE_PATCH:
case GLU_OUTLINE_POLYGON:
display_mode = value;
DisplayScene();
break;
case GLU_PATH_LENGTH:
case GLU_PARAMETRIC_ERROR:
case GLU_DOMAIN_DISTANCE:
case GLU_OBJECT_PARAMETRIC_ERROR:
case GLU_OBJECT_PATH_LENGTH:
sampling_method = value;
DisplayScene();
break;
case HOLE:
hole = !hole;
DisplayScene();
break;
case BRASS:
ambient = BrassAmbient;
diffuse = BrassDiffuse;
specular = BrassSpecular;
shininess = BrassShininess;
DisplayScene();
break;
case BRONZE:
ambient = BronzeAmbient;
diffuse = BronzeDiffuse;
specular = BronzeSpecular;
shininess = BronzeShininess;
DisplayScene();
break;
case POLISHED_BRONZE:
ambient = PolishedBronzeAmbient;
diffuse = PolishedBronzeDiffuse;
specular = PolishedBronzeSpecular;
shininess = PolishedBronzeShininess;
DisplayScene();
break;
case CHROME:
ambient = ChromeAmbient;
diffuse = ChromeDiffuse;
specular = ChromeSpecular;
shininess = ChromeShininess;
DisplayScene();
break;
case COPPER:
ambient = CopperAmbient;
diffuse = CopperDiffuse;
specular = CopperSpecular;
shininess = CopperShininess;
DisplayScene();
break;
case POLISHED_COPPER:
ambient = PolishedCopperAmbient;
diffuse = PolishedCopperDiffuse;
specular = PolishedCopperSpecular;
shininess = PolishedCopperShininess;
DisplayScene();
break;
case GOLD:
ambient = GoldAmbient;
diffuse = GoldDiffuse;
specular = GoldSpecular;
shininess = GoldShininess;
DisplayScene();
break;
case POLISHED_GOLD:
ambient = PolishedGoldAmbient;
diffuse = PolishedGoldDiffuse;
specular = PolishedGoldSpecular;
shininess = PolishedGoldShininess;
DisplayScene();
break;
case PEWTER:
ambient = PewterAmbient;
diffuse = PewterDiffuse;
specular = PewterSpecular;
shininess = PewterShininess;
DisplayScene();
break;
case SILVER:
ambient = SilverAmbient;
diffuse = SilverDiffuse;
specular = SilverSpecular;
shininess = SilverShininess;
DisplayScene();
break;
case POLISHED_SILVER:
ambient = PolishedSilverAmbient;
diffuse = PolishedSilverDiffuse;
specular = PolishedSilverSpecular;
shininess = PolishedSilverShininess;
DisplayScene();
break;
case EMERALD:
ambient = EmeraldAmbient;
diffuse = EmeraldDiffuse;
specular = EmeraldSpecular;
shininess = EmeraldShininess;
DisplayScene();
break;
case JADE:
ambient = JadeAmbient;
diffuse = JadeDiffuse;
specular = JadeSpecular;
shininess = JadeShininess;
DisplayScene();
break;
case OBSIDIAN:
ambient = ObsidianAmbient;
diffuse = ObsidianDiffuse;
specular = ObsidianSpecular;
shininess = ObsidianShininess;
DisplayScene();
break;
case PEARL:
ambient = PearlAmbient;
diffuse = PearlDiffuse;
specular = PearlSpecular;
shininess = PearlShininess;
DisplayScene();
break;
case RUBY:
ambient = RubyAmbient;
diffuse = RubyDiffuse;
specular = RubySpecular;
shininess = RubyShininess;
DisplayScene();
break;
case TURQUOISE:
ambient = TurquoiseAmbient;
diffuse = TurquoiseDiffuse;
specular = TurquoiseSpecular;
shininess = TurquoiseShininess;
DisplayScene();
break;
case BLACK_PLASTIC:
ambient = BlackPlasticAmbient;
diffuse = BlackPlasticDiffuse;
specular = BlackPlasticSpecular;
shininess = BlackPlasticShininess;
DisplayScene();
break;
case BLACK_RUBBER:
ambient = BlackRubberAmbient;
diffuse = BlackRubberDiffuse;
specular = BlackRubberSpecular;
shininess = BlackRubberShininess;
DisplayScene();
break;
case FULL_WINDOW:
aspect = FULL_WINDOW;
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
break;
case ASPECT_1_1:
aspect = ASPECT_1_1;
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
break;
case EXIT:
exit( 0 );
}
}
void ExtensionSetup()
{
const char * version =( char * ) glGetString( GL_VERSION );
int major = 0, minor = 0;
if( sscanf( version, "%d.%d", & major, & minor ) != 2 )
{
#ifdef WIN32
printf( "Błędny format wersji OpenGL\n" );
#else
printf( "Bledny format wersji OpenGL\n" );
#endif
exit( 0 );
}
if( major > 1 || minor >= 4 )
{
glWindowPos2i =( PFNGLWINDOWPOS2IPROC ) wglGetProcAddress( "glWindowPos2i" );
}
else
if( glutExtensionSupported( "GL_ARB_window_pos" ) )
{
glWindowPos2i =( PFNGLWINDOWPOS2IPROC ) wglGetProcAddress
( "glWindowPos2iARB" );
}
else
{
printf( "Brak rozszerzenia ARB_window_pos!\n" );
exit( 0 );
}
}
void GLUSetup()
{
const char * version =( char * ) gluGetString( GLU_VERSION );
int major = 0, minor = 0;
if( sscanf( version, "%d.%d", & major, & minor ) != 2 )
{
#ifdef WIN32
printf( "Błędny format wersji GLU\n" );
#else
printf( "Bledny format wersji GLU\n" );
#endif
exit( 0 );
}
if( major > 1 || minor >= 3 )
GLU_1_3 = true;
}
int main( int argc, char * argv[] )
{
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );
glutInitWindowSize( 500, 500 );
glutCreateWindow( "Powierzchnia NURBS" );
glutDisplayFunc( DisplayScene );
glutReshapeFunc( Reshape );
glutKeyboardFunc( Keyboard );
glutMouseFunc( MouseButton );
glutMotionFunc( MouseMotion );
glutCreateMenu( Menu );
GLUSetup();
int MenuDisplayMode = glutCreateMenu( Menu );
glutAddMenuEntry( "GLU_FILL", GLU_FILL );
glutAddMenuEntry( "GLU_OUTLINE_PATCH", GLU_OUTLINE_PATCH );
glutAddMenuEntry( "GLU_OUTLINE_POLYGON", GLU_OUTLINE_POLYGON );
int MenuSamplingMethod = glutCreateMenu( Menu );
glutAddMenuEntry( "GLU_PATH_LENGTH", GLU_PATH_LENGTH );
glutAddMenuEntry( "GLU_PARAMETRIC_ERROR", GLU_PARAMETRIC_ERROR );
glutAddMenuEntry( "GLU_DOMAIN_DISTANCE", GLU_DOMAIN_DISTANCE );
if( GLU_1_3 )
{
glutAddMenuEntry( "GLU_OBJECT_PARAMETRIC_ERROR", GLU_OBJECT_PARAMETRIC_ERROR );
glutAddMenuEntry( "GLU_OBJECT_PATH_LENGTH", GLU_OBJECT_PATH_LENGTH );
}
int MenuMaterial = glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Mosiądz", BRASS );
glutAddMenuEntry( "Brąz", BRONZE );
glutAddMenuEntry( "Polerowany brąz", POLISHED_BRONZE );
glutAddMenuEntry( "Chrom", CHROME );
glutAddMenuEntry( "Miedź", COPPER );
glutAddMenuEntry( "Polerowana miedź", POLISHED_COPPER );
glutAddMenuEntry( "Złoto", GOLD );
glutAddMenuEntry( "Polerowane złoto", POLISHED_GOLD );
glutAddMenuEntry( "Grafit (cyna z ołowiem)", PEWTER );
glutAddMenuEntry( "Srebro", SILVER );
glutAddMenuEntry( "Polerowane srebro", POLISHED_SILVER );
glutAddMenuEntry( "Szmaragd", EMERALD );
glutAddMenuEntry( "Jadeit", JADE );
glutAddMenuEntry( "Obsydian", OBSIDIAN );
glutAddMenuEntry( "Perła", PEARL );
glutAddMenuEntry( "Rubin", RUBY );
glutAddMenuEntry( "Turkus", TURQUOISE );
glutAddMenuEntry( "Czarny plastik", BLACK_PLASTIC );
glutAddMenuEntry( "Czarna guma", BLACK_RUBBER );
#else
glutAddMenuEntry( "Mosiadz", BRASS );
glutAddMenuEntry( "Braz", BRONZE );
glutAddMenuEntry( "Polerowany braz", POLISHED_BRONZE );
glutAddMenuEntry( "Chrom", CHROME );
glutAddMenuEntry( "Miedz", COPPER );
glutAddMenuEntry( "Polerowana miedz", POLISHED_COPPER );
glutAddMenuEntry( "Zloto", GOLD );
glutAddMenuEntry( "Polerowane zloto", POLISHED_GOLD );
glutAddMenuEntry( "Grafit (cyna z ołowiem)", PEWTER );
glutAddMenuEntry( "Srebro", SILVER );
glutAddMenuEntry( "Polerowane srebro", POLISHED_SILVER );
glutAddMenuEntry( "Szmaragd", EMERALD );
glutAddMenuEntry( "Jadeit", JADE );
glutAddMenuEntry( "Obsydian", OBSIDIAN );
glutAddMenuEntry( "Perla", PEARL );
glutAddMenuEntry( "Rubin", RUBY );
glutAddMenuEntry( "Turkus", TURQUOISE );
glutAddMenuEntry( "Czarny plastik", BLACK_PLASTIC );
glutAddMenuEntry( "Czarna guma", BLACK_RUBBER );
#endif
int MenuAspect = glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Aspekt obrazu - całe okno", FULL_WINDOW );
#else
glutAddMenuEntry( "Aspekt obrazu - cale okno", FULL_WINDOW );
#endif
glutAddMenuEntry( "Aspekt obrazu 1:1", ASPECT_1_1 );
glutCreateMenu( Menu );
glutAddSubMenu( "GLU_DISPLAY_MODE", MenuDisplayMode );
glutAddSubMenu( "GLU_SAMPLING_METHOD", MenuSamplingMethod );
#ifdef WIN32
glutAddMenuEntry( "Dziura włącz/wyłącz", HOLE );
glutAddSubMenu( "Materiał", MenuMaterial );
glutAddSubMenu( "Aspekt obrazu", MenuAspect );
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Dziura wlacz/wylacz", HOLE );
glutAddSubMenu( "Material", MenuMaterial );
glutAddSubMenu( "Aspekt obrazu", MenuAspect );
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
ExtensionSetup();
glutMainLoop();
return 0;
}