Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: Janusz Ganczarski
Biblioteki C++

NURBS

[lekcja] Rozdział 27. Obiekty (krzywe i powierzchnie) NURBS; wycinanie fragmentów powierzchni; dwa programy przykładowe.
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:
C/C++
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ę:
C/C++
void gluDeleteNurbsRenderer( GLUnurbs * nurb )

Krzywe NURBS

Definicję krzywej NURBS rozpoczyna wywołanie funkcji gluBeginCurve:
C/C++
void gluBeginCurve( GLUnurbs * nurb )
której jedynym parametrem jest wskaźnik do struktury (klasy) GLUnurbs.
Kształ krzywej NURBS definiuje funkcja gluNurbsCurve:
C/C++
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:
C/C++
void gluEndCurve( GLUnurbs * nurb )

Powierzchnie NURBS

Definicję powierzchni NURBS rozpoczyna wywołanie funkcji gluBeginSurface:
C/C++
void gluBeginSurface( GLUnurbs * nurb )
Kształt powierzchni NURBS określa funkcja gluNurbsSurface:
C/C++
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:
C/C++
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:
C/C++
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:
C/C++
void gluNurbsCallbackData( GLUnurbs * nurb, GLvoid * userData )
Funkcje zwrotne renderera obiektów NURBS określone są następującymi stałymi:
  • GLU_NURBS_BEGIN, GLU_NURBS_BEGIN_DATA - początek rysowania prymitywów graficznych, z których składa się obiekt NURBS; funkcje zwrotne mają postać:
    C/C++
    void begin( GLenum type )
    void beginData( GLenum type, void * userData )
    gdzie parametr type określa rodzaj rysowanych prymitywów i przyjmuje jedną z wartości: GL_LINES, GL_LINE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLE_STRIP, GL_TRIANGLES i GL_QUAD_STRIP,
  • GLU_NURBS_VERTEX, GLU_NURBS_VERTEX_DATA - współrzędne (x, y, z) wierzchołków prymitywów, które są zwracane w parametrze vertex funkcji:
    C/C++
    void vertex( GLfloat * vertex )
    void vertexData( GLfloat * vertex, void * userData )
  • GLU_NURBS_NORMAL, GLU_NURBS_NORMAL_DATA - współrzędne (x, y, z) wektorów normalnych, które są zwracane w parametrze normal funkcji:
    C/C++
    void normal( GLfloat * normal )
    void normalData( GLfloat * normal, void * userData )
  • GLU_NURBS_COLOR, GLU_NURBS_COLOR_DATA - składowe RGBA kolorów wierzchołków prymitywów, które są zwracane w parametrze color funkcji:
    C/C++
    void color( GLfloat * color )
    void colorData( GLfloat * color, void * userData )
  • GLU_NURBS_TEXTURE_COORD, GLU_NURBS_TEXTURE_COORD_DATA - współrzędne tekstury; ilość współrzędnych zależy od rodzaju wybranego ewaluatora; funkcje zwrotne mają postać:
    C/C++
    void texCoord( GLfloat * tex_coord )
    void texCoordData( GLfloat * tex_coord, void * userData )
  • GLU_NURBS_END, GLU_NURBS_END_DATA - koniec rysowania prymitywów;
    funkcje zwrotne mają postać:
    C/C++
    void end( void )
    void endData( void * userData )
  • GLU_NURBS_ERROR, GLU_ERROR - wystąpienie błędu podczas tworzenia krzywej lub powierzchni NURBS; kod błędu (patrz tabela 1) zwracany jest w parametrze errno funkcji zwrotnej:
    C/C++
    void error( GLenum errno )
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

numer błęduopis błędu
GLU_NURBS_ERROR1nieobsługiwana kolejność krzywej sklejanej
GLU_NURBS_ERROR2zbyt mała ilość węzłów
GLU_NURBS_ERROR3poprawny zakres węzłów jest pusty
GLU_NURBS_ERROR4ciąg węzłów zawiera zmniejszające się wartości węzłów
GLU_NURBS_ERROR5wielokrotność węzłów jest większa niż rząd krzywej sklejanej
GLU_NURBS_ERROR6gluEndCurve musi być za gluBeginCurve
GLU_NURBS_ERROR7gluBeginCurve musi poprzedzać gluEndCurve
GLU_NURBS_ERROR8brakujące lub nadmiarowe dane geometryczne
GLU_NURBS_ERROR9nie można narysować krzywych wycinających
GLU_NURBS_ERROR10brakujące lub nadmiarowe dane dziedziny
GLU_NURBS_ERROR11brakujące lub nadmiarowe dane dziedziny
GLU_NURBS_ERROR12gluEndTrim musi poprzedzać gluEndSurface
GLU_NURBS_ERROR13gluBeginSurface musi poprzedać gluEndSurface
GLU_NURBS_ERROR14krzywa niepoprawnego typu przekazana jako krzywa wycinająca
GLU_NURBS_ERROR15gluBeginSurface musi poprzedzać gluBeginTrim
GLU_NURBS_ERROR16gluEndTrim musi być za gluBeginTrim
GLU_NURBS_ERROR17gluBeginTrim musi poprzedzać gluEndTrim
GLU_NURBS_ERROR18błędna lub brak krzywej wycinającej
GLU_NURBS_ERROR19gluBeginTrim musi poprzedzać gluPwlCurve
GLU_NURBS_ERROR20podwójne odniesienie do krzywej wycinającej
GLU_NURBS_ERROR21połączenie krzywej wycinającej z krzywą NURBS
GLU_NURBS_ERROR22nieprawidłowe użycie danych krzywej wycinającej
GLU_NURBS_ERROR23podwójne odniesienie do krzywej NURBS
GLU_NURBS_ERROR24połączenie krzywej NURBS z krzywą wycinającą
GLU_NURBS_ERROR25podwójne odniesienie do powierzchni NURBS
GLU_NURBS_ERROR26błędna właściwość
GLU_NURBS_ERROR27gluEndSurface musi być za gluBeginSurface
GLU_NURBS_ERROR28przecinające się lub źle zorientowane krzywe wycinające
GLU_NURBS_ERROR29przecinające się krzywe wycinające
GLU_NURBS_ERROR30kod błędu nieużywany
GLU_NURBS_ERROR31rozłączne krzywe wycinające
GLU_NURBS_ERROR32nieznany błąd węzła
GLU_NURBS_ERROR33ujemna wartość licznika wierzchołków
GLU_NURBS_ERROR34ujemne przesunięcie bajtów danych
GLU_NURBS_ERROR35nieznany typ deskryptora
GLU_NURBS_ERROR36referencja do pustego punktu kontrolnego
GLU_NURBS_ERROR37podwojenie punktu w krzywej wycinającej

Właściwości NURBS

Modyfikcję właściwości krzywej lub powierzchni NURBS umożliwia funkcja gluNurbsProperty:
C/C++
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:
  • GLU_NURBS_TESSELLATOR - tryb kafelkowania, w którym obiekt NURBS jest dzielony (kafelkowany) na sekwencję prymitywów, ale współrzędne wierzchołków, wektorów normalnych i tekstur oraz składowe kolorów dostępne są wyłącznie za pośrednictwem funkcji zwrotnych,
  • GLU_NURBS_RENDERER - tryb renderowania krzywej lub powierzchni (wartość domyślna).
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

parametrdopuszczalne wartościwartość początkowa
GLU_NURBS_MODEGLU_NURBS_RENDERER,
GLU_NURBS_TESSELLATOR
GLU_NURBS_RENDERER
GLU_CULLINGGL_TRUE, GL_FALSEGL_FALSE
GLU_SAMPLING_METHODGLU_PATH_LENGTH,
GLU_PARAMETRIC_ERROR, GLU_DOMAIN_DISTANCE,
GLU_OBJECT_PARAMETRIC_ERROR, GLU_OBJECT_PATH_LENGTH
GLU_PATH_LENGTH
GLU_SAMPLING_TOLERANCEwartość dodatnia50
GLU_PARAMETRIC_TOLERANCEwartość dodatnia0,5
GLU_U_STEPwartość dodatnia100
GLU_V_STEPwartość dodatnia100
GLU_AUTO_LOAD_MATRIXGL_TRUE, GL_FALSEGL_TRUE
GLU_DISPLAY_MODEGLU_FILL,
GLU_OUTLINE_POLYGON, GLU_OUTLINE_PATCH
GLU_FILL

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:
  • GLU_PATH_LENGTH - długość boków, w pikselach, wielokątów składających się na renderowaną powierzchnię nie może przekraczać wartości określonej we właściwości GLU_SAMPLING_TOLERANCE; jest to wartość domyślna,
  • GLU_PARAMETRIC_ERROR - powierzchnia jest dzielona z użyciem właściwości GLU_PARAMETRIC_TOLERANCE, określającej maksymalną odległość, w pikselach, pomiędzy wielokątami podziału a całą powierzchnią,
  • GLU_DOMAIN_DISTANCE - ilość punktów próbkowania dla jednostki długości dla parametrów u i v określają stałe GLU_U_STEP i GLU_V_STEP,
  • GLU_OBJECT_PARAMETRIC_ERROR - powierzchnia jest dzielona z użyciem właściwości GLU_PARAMETRIC_TOLERANCE, określającej maksymalną odległość, w przestrzeni obiektu, pomiędzy wielokątami podziału a całą powierzchnią,
  • GLU_OBJECT_PATH_LENGTH - długość boków, w przestrzeni obiektu, wielokątów składających się na renderowaną powierzchnię nie może przekraczać wartości określonej we właściwości GLU_SAMPLING_TOLERANCE. Metody GLU_OBJECT_PARAMETRIC_ERROR i GLU_OBJECT_PATH_LENGTH zostały wprowadzone w wersji 1.3 biblioteki GLU w oparciu o rozszerzenie EXT object space tess.

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:
C/C++
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:
  • GLU_FILL - rysowane są wypełnione wielokąty,
  • GLU_OUTLINE_POLYGON - rysowane są krawędzie brzegowe (kontury) wielokątów składających się na powierzchnię,
  • GLU_OUTLINE_PATCH - rysowane są krzywe wycinania zdefiniowane przez użytkownika (domyślnie będzie to krzywa obejmująca brzeg całej powierzchni).

Pobieranie właściwości NURBS

Pobieranie właściwości obiektu NURBS umożliwia funkcja gluGetNurbsProperty:
C/C++
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:
C/C++
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:
  • GLU_MAP1_TRIM_2 - współrzędne odcinków krzywej zdefiniowane w dwuwymiarowej przestrzeni parametrycznej (s, t),
  • GLU_MAP1_TRIM_3 - współrzędne odcinków krzywej zdefiniowane w dwuwymiarowej przestrzeni parametrycznej (s, t, q), gdzie parametr q jest współczynnikiem skalowania.
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:
C/C++
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.
Rysunek 1. Program Krzywa NURBS - wartość GLU_SAMPLING_TOLERANCE równa 10
Rysunek 1. Program Krzywa NURBS - wartość GLU_SAMPLING_TOLERANCE równa 10
Rysunek 2. Program Krzywa NURBS - wartość GLU_SAMPLING_TOLERANCE równa 150
Rysunek 2. Program Krzywa NURBS - wartość GLU_SAMPLING_TOLERANCE równa 150

Plik krzywa_nurbs.cpp

C/C++
/*
(c) Janusz Ganczarski
http://www.januszg.hg.pl
JanuszG@enter.net.pl
*/

#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "colors.h"

// stałe do obsługi menu podręcznego

enum
{
    EXIT // wyjście
};

// wskaźnik naciśnięcia lewego przycisku myszki

int button_state = GLUT_UP;

// położenie kursora myszki

int button_x, button_y;

// punkty kontrolne krzywej

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
};

// węzły

GLfloat knots[ 5 * 2 ] =
{
    0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0
};

// wartość właściwości GLU_SAMPLING_TOLERANCE

GLfloat sampling_tolerance = 20.0;

// identyfikatory wyświetlanych obiektów

enum
{
    NONE,
    CURVE,
    POINT_0,
    POINT_1,
    POINT_2,
    POINT_3,
    POINT_4
};

// identyfikator wybranego obiektu

int select_object = NONE;

// funkcja rysująca napis w wybranym miejscu

void DrawString( GLfloat x, GLfloat y, char * string )
{
    // położenie napisu
    glRasterPos2f( x, y );
   
    // wyświetlenie napisu
    int len = strlen( string );
    for( int i = 0; i < len; i++ )
         glutBitmapCharacter( GLUT_BITMAP_9_BY_15, string[ i ] );
   
}

// funkcja generująca scenę 3D

void Display()
{
    // kolor tła - zawartość bufora koloru
    glClearColor( 1.0, 1.0, 1.0, 1.0 );
   
    // czyszczenie bufora koloru
    glClear( GL_COLOR_BUFFER_BIT );
   
    // inicjalizacja stosu nazw obiektów
    glInitNames();
   
    // umieszczenie nazwy na stosie nazw, aby nie był on pusty
    glPushName( NONE );
   
    // obiekt CURVE
    glLoadName( CURVE );
   
    // ustawienie grubości linii
    glLineWidth( 2.0 );
   
    // kolor krzywej
    glColor3fv( Black );
   
    // utworzenie obiektu NURBS
    GLUnurbsObj * nurbs = gluNewNurbsRenderer();
   
    // początek definicji krzywej NURBS
    gluBeginCurve( nurbs );
   
    // ustawienie właściwości GLU_SAMPLING_TOLERANCE
    gluNurbsProperty( nurbs, GLU_SAMPLING_TOLERANCE, sampling_tolerance );
   
    // ewaluacja krzywej
    gluNurbsCurve( nurbs, 10, knots, 3, points, 5, GL_MAP1_VERTEX_3 );
   
    // koniec definicji krzywej
    gluEndCurve( nurbs );
   
    // usunięcie obiektu NURBS
    gluDeleteNurbsRenderer( nurbs );
   
    // kolor punktów
    glColor3fv( Red );
   
    // narysowanie punktów kontrolnych
    for( int i = 0; i < 5; i++ )
    {
        // nazwa obiektu
        glLoadName( POINT_0 + i );
       
        // punkt kontrolny
        glRectf( points[ i * 3 ] - 5, points[ i * 3 + 1 ] - 5, points[ i * 3 ] + 5, points[ i * 3 + 1 ] + 5 );
    }
   
    // wyświetlenie wartości właściwości GLU_SAMPLING_TOLERANCE
    char string[ 100 ];
    glColor3fv( Black );
    sprintf( string, "GLU_SAMPLING_TOLERANCE = %f", sampling_tolerance );
   
    // narysowanie napisu
    DrawString( 2, 2, string );
   
    // skierowanie poleceń do wykonania
    glFlush();
   
    // zamiana buforów koloru
    glutSwapBuffers();
}

// zmiana wielkości okna

void Reshape( int width, int height )
{
    // obszar renderingu - całe okno
    glViewport( 0, 0, width, height );
   
    // wybór macierzy rzutowania
    glMatrixMode( GL_PROJECTION );
   
    // macierz rzutowania = macierz jednostkowa
    glLoadIdentity();
   
    // rzutowanie prostokątne
    gluOrtho2D( 0, width, 0, height );
   
    // generowanie sceny 3D
    Display();
}

// obsługa selekcji obietków

void Selection( int x, int y )
{
    // wielkość bufora selekcji
    const int BUFFER_LENGTH = 64;
   
    // bufor selekcji
    GLuint select_buffer[ BUFFER_LENGTH ];
   
    // przygotowanie bufora selekcji
    glSelectBuffer( BUFFER_LENGTH, select_buffer );
   
    // pobranie obszaru rozmiaru renderingu
    int viewport[ 4 ];
    glGetIntegerv( GL_VIEWPORT, viewport );
   
    // szerokość i wysokość obszaru renderingu
    int width = viewport[ 2 ];
    int height = viewport[ 3 ];
   
    // wybór macierzy rzutowania
    glMatrixMode( GL_PROJECTION );
   
    // odłożenie macierzy rzutowania na stos
    glPushMatrix();
   
    // macierz rzutowania = macierz jednostkowa
    glLoadIdentity();
   
    // parametry bryły obcinania - jednostkowa kostka dookoła punktu wskaźnika
    // myszy (x,y) rozciągającej się na dwa piksele w poziomie i w pionie
    gluPickMatrix( x, height - y, 2, 2, viewport );
   
    // rzutowanie prostokątne
    gluOrtho2D( 0, width, 0, height );
   
    // włączenie trybu selekcji
    glRenderMode( GL_SELECT );
   
    // generowanie sceny 3D
    Display();
   
    // zliczenie ilości rekordów trafień, powrót do domyślnego trybu renderingu
    GLint hits = glRenderMode( GL_RENDER );
   
    // wybór macierzy rzutowania
    glMatrixMode( GL_PROJECTION );
   
    // zdjęcie macierzy rzutowania ze stosu
    glPopMatrix();
   
    // domyślnie w wyniku selekcji nie wybrano żadnego punktu kontrolnego
    select_object = NONE;
   
    // w wyniku selekcji wybrano co najmniej jeden obiekt
    // dla uproszczenia sprawdzamy tylko pierwszy obiekt na stosie
    if( hits > 0 )
    if( select_buffer[ 3 ] > CURVE )
         select_object = select_buffer[ 3 ];
   
}

// obsługa przycisków myszki

void MouseButton( int button, int state, int x, int y )
{
    if( button == GLUT_LEFT_BUTTON )
    {
        // obsługa selekcji obiektów
        Selection( x, y );
        glutPostRedisplay();
       
        // zapamiętanie stanu lewego przycisku myszki
        button_state = state;
       
        // zapamiętanie położenia kursora myszki
        if( state == GLUT_DOWN )
        {
            button_x = x;
            button_y = y;
        }
    }
}

// obsługa ruchu kursora myszki

void MouseMotion( int x, int y )
{
    // sprawdzenie czy wskaźnik myszy nie znajduje się poza obszarem okna
    if( x > 0 & x < glutGet( GLUT_WINDOW_WIDTH ) & y > 0 & y < glutGet( GLUT_WINDOW_HEIGHT ) )
    if( button_state == GLUT_DOWN )
    {
        // zmiana współrzędnych punktów
        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;
        }
       
        // aktualizacja położenia myszki
        button_x = x;
        button_y = y;
       
        // wyświetlenie sceny
        glutPostRedisplay();
    }
}

// obsługa klawiatury

void Keyboard( unsigned char key, int x, int y )
{
    switch( key )
    {
        // klawisz "+" -
    case '+':
        sampling_tolerance += 10.0;
        break;
       
        // klawisz "-" -
    case '-':
        if( sampling_tolerance > 10.0 )
             sampling_tolerance -= 10.0;
       
        break;
    }
   
    // narysowanie sceny
    Display();
}

// obsługa menu podręcznego

void Menu( int value )
{
    switch( value )
    {
        // wyjście
    case EXIT:
        exit( 0 );
    }
}

int main( int argc, char * argv[] )
{
    // inicjalizacja biblioteki GLUT
    glutInit( & argc, argv );
   
    // inicjalizacja bufora ramki
    glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
   
    // rozmiary głównego okna programu
    glutInitWindowSize( 500, 500 );
   
    // utworzenie głównego okna programu
    glutCreateWindow( "Krzywa NURBS" );
   
    // dołączenie funkcji generującej scenę 3D
    glutDisplayFunc( Display );
   
    // dołączenie funkcji wywoływanej przy zmianie rozmiaru okna
    glutReshapeFunc( Reshape );
   
    // dołączenie funkcji obsługi klawiatury
    glutKeyboardFunc( Keyboard );
   
    // obsługa przycisków myszki
    glutMouseFunc( MouseButton );
   
    // obsługa ruchu kursora myszki
    glutMotionFunc( MouseMotion );
   
    // utworzenie menu podręcznego
    glutCreateMenu( Menu );
   
    // menu główne
    glutCreateMenu( Menu );
    #ifdef WIN32
   
    glutAddMenuEntry( "Wyjście", EXIT );
    #else
   
    glutAddMenuEntry( "Wyjscie", EXIT );
    #endif
   
    // określenie przycisku myszki obsługującej menu podręczne
    glutAttachMenu( GLUT_RIGHT_BUTTON );
   
    // wprowadzenie programu do obsługi pętli komunikatów
    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).
Rysunek 3. Program Powierzchnia NURBS - metoda podziału na wielokąty GLU_PATH_LENGTH
Rysunek 3. Program Powierzchnia NURBS - metoda podziału na wielokąty GLU_PATH_LENGTH

Plik powierzchnia_nurbs.cpp

Rysunek 4. Program Powierzchnia NURBS - metoda podziału na wielokąty GLU_PARAMETRIC_ERROR
Rysunek 4. Program Powierzchnia NURBS - metoda podziału na wielokąty GLU_PARAMETRIC_ERROR
Rysunek 5. Program Powierzchnia NURBS - metoda podziału na wielokąty GLU_DOMAIN_DISTANCE
Rysunek 5. Program Powierzchnia NURBS - metoda podziału na wielokąty GLU_DOMAIN_DISTANCE
Rysunek 6. Program Powierzchnia NURBS - metoda podziału na wielokąty GLU_PATH_LENGTH (obiekt pomniejszony)
Rysunek 6. Program Powierzchnia NURBS - metoda podziału na wielokąty GLU_PATH_LENGTH (obiekt pomniejszony)
Rysunek 7. Program Powierzchnia NURBS - metoda podziału na wielokąty GLU_PARAMETRIC_ERROR (obiekt pomniejszony)
Rysunek 7. Program Powierzchnia NURBS - metoda podziału na wielokąty GLU_PARAMETRIC_ERROR (obiekt pomniejszony)
Rysunek 8. Program Powierzchnia NURBS - metoda podziału na wielokąty GLU_DOMAIN_DISTANCE (obiekt pomniejszony)
Rysunek 8. Program Powierzchnia NURBS - metoda podziału na wielokąty GLU_DOMAIN_DISTANCE (obiekt pomniejszony)
Rysunek 9. Program Powierzchnia NURBS - powierzchnia wypełniona
Rysunek 9. Program Powierzchnia NURBS - powierzchnia wypełniona
Rysunek 10. Program Powierzchnia NURBS - powierzchnia wypełniona z dziurą
Rysunek 10. Program Powierzchnia NURBS - powierzchnia wypełniona z dziurą
Rysunek 11. Program Powierzchnia NURBS - powierzchnia z zaznaczonymi krzywymi wycinania
Rysunek 11. Program Powierzchnia NURBS - powierzchnia z zaznaczonymi krzywymi wycinania
C/C++
/*
(c) Janusz Ganczarski
http://www.januszg.hg.pl
JanuszG@enter.net.pl
*/

#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"

// wskaźnik na funkcję glWindowPos2i

PFNGLWINDOWPOS2IPROC glWindowPos2i = NULL;

// stałe do obsługi menu podręcznego

enum
{
    HOLE, // dziura
   
    // materiały
    BRASS, // mosiądz
    BRONZE, // brąz
    POLISHED_BRONZE, // polerowany brąz
    CHROME, // chrom
    COPPER, // miedź
    POLISHED_COPPER, // polerowana miedź
    GOLD, // złoto
    POLISHED_GOLD, // polerowane złoto
    PEWTER, // grafit (cyna z ołowiem)
    SILVER, // srebro
    POLISHED_SILVER, // polerowane srebro
    EMERALD, // szmaragd
    JADE, // jadeit
    OBSIDIAN, // obsydian
    PEARL, // perła
    RUBY, // rubin
    TURQUOISE, // turkus
    BLACK_PLASTIC, // czarny plastik
    BLACK_RUBBER, // czarna guma
   
    // obszar renderingu
    FULL_WINDOW, // aspekt obrazu - całe okno
    ASPECT_1_1, // aspekt obrazu 1:1
    EXIT // wyjście
};

// aspekt obrazu

int aspect = FULL_WINDOW;

// usunięcie definicji makr near i far

#ifdef near
#undef near
#endif
#ifdef far
#undef far
#endif

// rozmiary bryły obcinania

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;

// kąty obrotu obiektu

GLfloat rotatex = 0.0;
GLfloat rotatey = 0.0;

// wskaźnik naciśnięcia lewego przycisku myszki

int button_state = GLUT_UP;

// położenie kursora myszki

int button_x, button_y;

// współczynnik skalowania

GLfloat scale = 1.0;

// właściwości materiału - domyślnie mosiądz

const GLfloat * ambient = BrassAmbient;
const GLfloat * diffuse = BrassDiffuse;
const GLfloat * specular = BrassSpecular;
GLfloat shininess = BrassShininess;

// metoda podziału powierzchni NURBS na wielokąty

int sampling_method = GLU_PATH_LENGTH;

// sposób renderowania powierzchni NURBS

int display_mode = GLU_FILL;

// współrzędne punktów kontrolnych powierzchni

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
};

// węzły

GLfloat knots[ 4 * 2 ] =
{
    0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0
};

// współrzędne zewnętrznej krzywej PWL

GLfloat outline_pwl[ 10 ] =
{
    0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0
};

// współrzędne krzywej PWL opisującej wycinany fragment powierzchni

GLfloat hole_pwl[ 10 ] =
{
    0.25, 0.25, 0.25, 0.75, 0.75, 0.75, 0.75, 0.25, 0.25, 0.25
};

// znacznik dostępności biblioteki GLU w wersji 1.3

bool GLU_1_3 = false;

// definicje metod podziału powierzchni NURBS na
// wielokąty wprowadzone w wersji 1.3 biblioteki GLU

#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

// znacznik czy wycinać fragment powierzchni NURBS

bool hole = false;

// funkcja rysująca napis w wybranym miejscu
// (wersja korzystająca z funkcji glWindowPos2i)

void DrawString( GLint x, GLint y, char * string )
{
    // położenie napisu
    glWindowPos2i( x, y );
   
    // wyświetlenie napisu
    int len = strlen( string );
    for( int i = 0; i < len; i++ )
         glutBitmapCharacter( GLUT_BITMAP_9_BY_15, string[ i ] );
   
}

// funkcja generująca scenę 3D

void DisplayScene()
{
    // kolor tła - zawartość bufora koloru
    glClearColor( 1.0, 1.0, 1.0, 1.0 );
   
    // czyszczenie bufora koloru i bufora głębokości
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
   
    // wybór macierzy modelowania
    glMatrixMode( GL_MODELVIEW );
   
    // macierz modelowania = macierz jednostkowa
    glLoadIdentity();
   
    // włączenie testu bufora głębokości
    glEnable( GL_DEPTH_TEST );
   
    // przesunięcie układu współrzędnych obiektu do środka bryły odcinania
    glTranslatef( 0, 0, -( near + far ) / 2 );
   
    // obroty obiektu
    glRotatef( rotatex, 1.0, 0, 0 );
    glRotatef( rotatey, 0, 1.0, 0 );
   
    // skalowanie obiektu - klawisze "+" i "-"
    glScalef( scale, scale, scale );
   
    // włączenie efektów oświetlenia, gry renderowana jest wypełniona powierzchnia
    if( display_mode == GLU_FILL )
    {
        // włączenie oświetlenia
        glEnable( GL_LIGHTING );
       
        // włączenie światła GL_LIGHT0 z parametrami domyślnymi
        glEnable( GL_LIGHT0 );
       
        // włączenie automatycznej normalizacji wektorów normalnych
        glEnable( GL_NORMALIZE );
       
        // właściwości materiału
        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 );
       
        // włączenie automatycznego generowania wektorów normalnych
        glEnable( GL_AUTO_NORMAL );
    }
   
    // kolor krawędzi
    glColor3fv( Black );
   
    // utworzenie obiektu NURBS
    GLUnurbsObj * nurbs = gluNewNurbsRenderer();
   
    // początek definicji powierzchni NURBS
    gluBeginSurface( nurbs );
   
    // sposób renderowania powierzchni NURBS
    gluNurbsProperty( nurbs, GLU_DISPLAY_MODE, display_mode );
   
    // metoda podziału powierzchni NURBS na wielokąty
    gluNurbsProperty( nurbs, GLU_SAMPLING_METHOD, sampling_method );
   
    // narysowanie powierzchni
    gluNurbsSurface( nurbs, 8, knots, 8, knots, 4 * 3, 3, points, 4, 4, GL_MAP2_VERTEX_3 );
   
    // rysowanie dziury w powierzchni NURBS
    if( hole )
    {
        // zewnętrzna krzywa wycinająca
        gluBeginTrim( nurbs );
        gluPwlCurve( nurbs, 5, outline_pwl, 2, GLU_MAP1_TRIM_2 );
        gluEndTrim( nurbs );
       
        // wewnętrzna krzywa wycinająca
        gluBeginTrim( nurbs );
        gluPwlCurve( nurbs, 5, hole_pwl, 2, GLU_MAP1_TRIM_2 );
        gluEndTrim( nurbs );
    }
   
    // koniec definicji powierzchni
    gluEndSurface( nurbs );
   
    // usunięcie obiektu NURBS
    gluDeleteNurbsRenderer( nurbs );
   
    // wyłączenie automatycznego generowania wektorów normalnych
    glDisable( GL_AUTO_NORMAL );
   
    // wyłączenie automatycznej normalizacji wektorów normalnych
    glDisable( GL_NORMALIZE );
   
    // wyłączenie światła GL_LIGHT0
    glDisable( GL_LIGHT0 );
   
    // wyłaczenie oświetlenia
    glDisable( GL_LIGHTING );
   
    // kolor punktów
    glColor3fv( Blue );
   
    // rozxmiar punktów
    glPointSize( 6.0 );
   
    // narysowanie punktów kontrolnych
    glBegin( GL_POINTS );
    for( int i = 0; i < 12; i++ )
         glVertex3fv( points + i * 3 );
   
    glEnd();
   
    // wyświetlenie informacji o wybranych właściwościach powierzchni NURBS
    glColor3fv( Black );
   
    // metoda podziału powierzchni NURBS na wielokąty
    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" );
   
    // metody podziału wprowadzone w wersji 1.3 biblioteki GLU
    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" );
       
    }
   
    // sposób renderowania powierzchni NURBS
    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" );
   
    // skierowanie poleceń do wykonania
    glFlush();
   
    // zamiana buforów koloru
    glutSwapBuffers();
}

// zmiana wielkości okna

void Reshape( int width, int height )
{
    // obszar renderingu - całe okno
    glViewport( 0, 0, width, height );
   
    // wybór macierzy rzutowania
    glMatrixMode( GL_PROJECTION );
   
    // macierz rzutowania = macierz jednostkowa
    glLoadIdentity();
   
    // parametry bryły obcinania
    if( aspect == ASPECT_1_1 )
    {
        // wysokość okna większa od wysokości okna
        if( width < height && width > 0 )
             glFrustum( left, right, bottom * height / width, top * height / width, near, far );
        else
       
        // szerokość okna większa lub równa wysokości okna
        if( width >= height && height > 0 )
             glFrustum( left * width / height, right * width / height, bottom, top, near, far );
       
    }
    else
         glFrustum( left, right, bottom, top, near, far );
   
    // generowanie sceny 3D
    DisplayScene();
}

// obsługa klawiatury

void Keyboard( unsigned char key, int x, int y )
{
    // klawisz +
    if( key == '+' )
         scale += 0.05;
    else
   
    // klawisz -
    if( key == '-' && scale > 0.05 )
         scale -= 0.05;
   
    // narysowanie sceny
    DisplayScene();
}

// obsługa przycisków myszki

void MouseButton( int button, int state, int x, int y )
{
    if( button == GLUT_LEFT_BUTTON )
    {
        // zapamiętanie stanu lewego przycisku myszki
        button_state = state;
       
        // zapamiętanie położenia kursora myszki
        if( state == GLUT_DOWN )
        {
            button_x = x;
            button_y = y;
        }
    }
}

// obsługa ruchu kursora myszki

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();
    }
}

// obsługa menu podręcznego

void Menu( int value )
{
    switch( value )
    {
        // GLU_DISPLAY_MODE
    case GLU_FILL:
    case GLU_OUTLINE_PATCH:
    case GLU_OUTLINE_POLYGON:
        display_mode = value;
        DisplayScene();
        break;
       
        // GLU_SAMPLING_METHOD
    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;
       
        // dziura włącz/wyłącz
    case HOLE:
        hole = !hole;
        DisplayScene();
        break;
       
        // materiał - mosiądz
    case BRASS:
        ambient = BrassAmbient;
        diffuse = BrassDiffuse;
        specular = BrassSpecular;
        shininess = BrassShininess;
        DisplayScene();
        break;
       
        // materiał - brąz
    case BRONZE:
        ambient = BronzeAmbient;
        diffuse = BronzeDiffuse;
        specular = BronzeSpecular;
        shininess = BronzeShininess;
        DisplayScene();
        break;
       
        // materiał - polerowany brąz
    case POLISHED_BRONZE:
        ambient = PolishedBronzeAmbient;
        diffuse = PolishedBronzeDiffuse;
        specular = PolishedBronzeSpecular;
        shininess = PolishedBronzeShininess;
        DisplayScene();
        break;
       
        // materiał - chrom
    case CHROME:
        ambient = ChromeAmbient;
        diffuse = ChromeDiffuse;
        specular = ChromeSpecular;
        shininess = ChromeShininess;
        DisplayScene();
        break;
       
        // materiał - miedź
    case COPPER:
        ambient = CopperAmbient;
        diffuse = CopperDiffuse;
        specular = CopperSpecular;
        shininess = CopperShininess;
        DisplayScene();
        break;
       
        // materiał - polerowana miedź
    case POLISHED_COPPER:
        ambient = PolishedCopperAmbient;
        diffuse = PolishedCopperDiffuse;
        specular = PolishedCopperSpecular;
        shininess = PolishedCopperShininess;
        DisplayScene();
        break;
       
        // materiał - złoto
    case GOLD:
        ambient = GoldAmbient;
        diffuse = GoldDiffuse;
        specular = GoldSpecular;
        shininess = GoldShininess;
        DisplayScene();
        break;
       
        // materiał - polerowane złoto
    case POLISHED_GOLD:
        ambient = PolishedGoldAmbient;
        diffuse = PolishedGoldDiffuse;
        specular = PolishedGoldSpecular;
        shininess = PolishedGoldShininess;
        DisplayScene();
        break;
       
        // materiał - grafit (cyna z ołowiem)
    case PEWTER:
        ambient = PewterAmbient;
        diffuse = PewterDiffuse;
        specular = PewterSpecular;
        shininess = PewterShininess;
        DisplayScene();
        break;
       
        // materiał - srebro
    case SILVER:
        ambient = SilverAmbient;
        diffuse = SilverDiffuse;
        specular = SilverSpecular;
        shininess = SilverShininess;
        DisplayScene();
        break;
       
        // materiał - polerowane srebro
    case POLISHED_SILVER:
        ambient = PolishedSilverAmbient;
        diffuse = PolishedSilverDiffuse;
        specular = PolishedSilverSpecular;
        shininess = PolishedSilverShininess;
        DisplayScene();
        break;
       
        // materiał - szmaragd
    case EMERALD:
        ambient = EmeraldAmbient;
        diffuse = EmeraldDiffuse;
        specular = EmeraldSpecular;
        shininess = EmeraldShininess;
        DisplayScene();
        break;
       
        // materiał - jadeit
    case JADE:
        ambient = JadeAmbient;
        diffuse = JadeDiffuse;
        specular = JadeSpecular;
        shininess = JadeShininess;
        DisplayScene();
        break;
       
        // materiał - obsydian
    case OBSIDIAN:
        ambient = ObsidianAmbient;
        diffuse = ObsidianDiffuse;
        specular = ObsidianSpecular;
        shininess = ObsidianShininess;
        DisplayScene();
        break;
       
        // materiał - perła
    case PEARL:
        ambient = PearlAmbient;
        diffuse = PearlDiffuse;
        specular = PearlSpecular;
        shininess = PearlShininess;
        DisplayScene();
        break;
       
        // metariał - rubin
    case RUBY:
        ambient = RubyAmbient;
        diffuse = RubyDiffuse;
        specular = RubySpecular;
        shininess = RubyShininess;
        DisplayScene();
        break;
       
        // materiał - turkus
    case TURQUOISE:
        ambient = TurquoiseAmbient;
        diffuse = TurquoiseDiffuse;
        specular = TurquoiseSpecular;
        shininess = TurquoiseShininess;
        DisplayScene();
        break;
       
        // materiał - czarny plastik
    case BLACK_PLASTIC:
        ambient = BlackPlasticAmbient;
        diffuse = BlackPlasticDiffuse;
        specular = BlackPlasticSpecular;
        shininess = BlackPlasticShininess;
        DisplayScene();
        break;
       
        // materiał - czarna guma
    case BLACK_RUBBER:
        ambient = BlackRubberAmbient;
        diffuse = BlackRubberDiffuse;
        specular = BlackRubberSpecular;
        shininess = BlackRubberShininess;
        DisplayScene();
        break;
       
        // obszar renderingu - całe okno
    case FULL_WINDOW:
        aspect = FULL_WINDOW;
        Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
        break;
       
        // obszar renderingu - aspekt 1:1
    case ASPECT_1_1:
        aspect = ASPECT_1_1;
        Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
        break;
       
        // wyjście
    case EXIT:
        exit( 0 );
    }
}

// sprawdzenie i przygotowanie obsługi wybranych rozszerzeń

void ExtensionSetup()
{
    // pobranie numeru wersji biblioteki OpenGL
    const char * version =( char * ) glGetString( GL_VERSION );
   
    // odczyt wersji OpenGL
    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 );
    }
   
    // sprawdzenie czy jest co najmniej wersja 1.4
    if( major > 1 || minor >= 4 )
    {
        // pobranie wskaźnika wybranej funkcji OpenGL 1.4
        glWindowPos2i =( PFNGLWINDOWPOS2IPROC ) wglGetProcAddress( "glWindowPos2i" );
    }
    else
    // sprawdzenie czy jest obsługiwane rozszerzenie ARB_window_pos
    if( glutExtensionSupported( "GL_ARB_window_pos" ) )
    {
        // pobranie wskaźnika wybranej funkcji rozszerzenia ARB_window_pos
        glWindowPos2i =( PFNGLWINDOWPOS2IPROC ) wglGetProcAddress
        ( "glWindowPos2iARB" );
    }
    else
    {
        printf( "Brak rozszerzenia ARB_window_pos!\n" );
        exit( 0 );
    }
}

// sprawdzenie numeru wersji biblioteki GLU

void GLUSetup()
{
    // pobranie numeru wersji biblioteki GLU
    const char * version =( char * ) gluGetString( GLU_VERSION );
   
    // sprawdzenie numeru wersji biblioteki GLU
    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 );
    }
   
    // sprawdzenie czy jest co najmniej wersja 1.3 biblioteki GLU
    if( major > 1 || minor >= 3 )
         GLU_1_3 = true;
   
}

int main( int argc, char * argv[] )
{
    // inicjalizacja biblioteki GLUT
    glutInit( & argc, argv );
   
    // inicjalizacja bufora ramki
    glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );
   
    // rozmiary głównego okna programu
    glutInitWindowSize( 500, 500 );
   
    // utworzenie głównego okna programu
    glutCreateWindow( "Powierzchnia NURBS" );
   
    // dołączenie funkcji generującej scenę 3D
    glutDisplayFunc( DisplayScene );
   
    // dołączenie funkcji wywoływanej przy zmianie rozmiaru okna
    glutReshapeFunc( Reshape );
   
    // dołączenie funkcji obsługi klawiatury
    glutKeyboardFunc( Keyboard );
   
    // obsługa przycisków myszki
    glutMouseFunc( MouseButton );
   
    // obsługa ruchu kursora myszki
    glutMotionFunc( MouseMotion );
   
    // utworzenie menu podręcznego
    glutCreateMenu( Menu );
   
    // sprawdzenie numeru wersji biblioteki GLU
    GLUSetup();
   
    // utworzenie podmenu - GLU_DISPLAY_MODE
    int MenuDisplayMode = glutCreateMenu( Menu );
    glutAddMenuEntry( "GLU_FILL", GLU_FILL );
    glutAddMenuEntry( "GLU_OUTLINE_PATCH", GLU_OUTLINE_PATCH );
    glutAddMenuEntry( "GLU_OUTLINE_POLYGON", GLU_OUTLINE_POLYGON );
   
    // utworzenie podmenu - GLU_SAMPLING_METHOD
    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 );
   
    // elementy munu dostępne w wersji 1.3 biblioteki GLU
    if( GLU_1_3 )
    {
        glutAddMenuEntry( "GLU_OBJECT_PARAMETRIC_ERROR", GLU_OBJECT_PARAMETRIC_ERROR );
        glutAddMenuEntry( "GLU_OBJECT_PATH_LENGTH", GLU_OBJECT_PATH_LENGTH );
    }
   
    // utworzenie podmenu - Materiał
    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
   
    // utworzenie podmenu - Aspekt obrazu
    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 );
   
    // menu główne
    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
   
    // określenie przycisku myszki obsługującej menu podręczne
    glutAttachMenu( GLUT_RIGHT_BUTTON );
   
    // sprawdzenie i przygotowanie obsługi wybranych rozszerzeń
    ExtensionSetup();
   
    // wprowadzenie programu do obsługi pętli komunikatów
    glutMainLoop();
    return 0;
}
Poprzedni dokument Następny dokument
Krzywe i powierzchnie Beziera Test zasłaniania